From f7ff77d865ca9f55d02abfea71573baedd1f188d Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Mon, 17 Oct 2022 11:02:04 -0700 Subject: [PATCH 1/3] Make fixes and improvements - Invent iso8601us() for faster timestamps - Improve --strace descriptions of sigset_t - Rebuild the Landlock Make bootstrap binary - Introduce MODE=sysv for non-Windows builds - Permit OFD fcntl() locks under pledge(flock) - redbean can now protect your kernel from ddos - Have vfork() fallback to sys_fork() not fork() - Change kmalloc() to not die when out of memory - Improve documentation for some termios functions - Rewrite putenv() and friends to conform to POSIX - Fix linenoise + strace verbosity issue on Windows - Fix regressions in our ability to show backtraces - Change redbean SetHeader() to no-op if value is nil - Improve fcntl() so SQLite locks work in non-WAL mode - Remove some unnecessary work during fork() on Windows - Create redbean-based SSL reverse proxy for IPv4 TurfWar - Fix ape/apeinstall.sh warning when using non-bash shells - Add ProgramTrustedIp(), and IsTrustedIp() APIs to redbean - Support $PWD, $UID, $GID, and $EUID in command interpreter - Introduce experimental JTqFpD APE prefix for non-Windows builds - Invent blackhole daemon for firewalling IP addresses via UNIX named socket - Add ProgramTokenBucket(), AcquireToken(), and CountTokens() APIs to redbean --- ape/ape.S | 10 +- ape/apeinstall.sh | 4 +- ape/apeuninstall.sh | 4 + build/bootstrap/make.com | Bin 497450 -> 502045 bytes build/config.mk | 33 +- examples/auto-memory-safety-crash2.c | 12 +- examples/crashreport.c | 7 + examples/examples.mk | 19 + examples/nesemu1.cc | 2 +- examples/symtab.c | 42 ++ libc/calls/__sig_mask.c | 2 + libc/calls/blocksigs.internal.h | 19 + libc/calls/calls.h | 84 ++-- libc/calls/cfspeed.c | 88 ++++ libc/calls/close-nt.c | 9 +- libc/calls/close.c | 4 +- libc/calls/closefrom.c | 7 +- libc/calls/dup-nt.c | 35 +- libc/calls/execve-sysv.c | 57 ++- libc/calls/execve.c | 6 +- libc/calls/fcntl-nt.c | 265 +++++++++-- libc/calls/fcntl.c | 27 +- libc/calls/internal.h | 3 +- libc/calls/kill.c | 6 +- libc/calls/open-nt.c | 2 +- libc/calls/pipe-nt.c | 6 +- libc/calls/pledge-linux.c | 27 +- libc/calls/preadv.c | 19 +- libc/calls/pwritev.c | 19 +- libc/{intrin => calls}/releasefd.c | 15 +- libc/calls/reservefd.c | 14 +- libc/calls/sigaction.c | 4 + .../sigblockall.c} | 19 +- libc/calls/sigenter-openbsd.c | 4 +- libc/calls/sigenter-xnu.c | 1 + ...rocmask-sysv.greg.c => sigprocmask-sysv.c} | 0 libc/calls/sigprocmask.c | 1 - libc/calls/{cfsetospeed.c => sigsetmask.c} | 34 +- libc/calls/struct/fd.internal.h | 2 +- libc/calls/struct/flock.h | 2 +- libc/calls/struct/sigset.h | 2 + libc/calls/syscall-sysv.internal.h | 3 +- libc/calls/tcdrain.c | 37 +- libc/calls/tcflow.c | 88 +++- libc/calls/tcflush.c | 65 ++- libc/calls/tcgetpgrp.c | 23 +- libc/calls/tcsendbreak.c | 53 ++- libc/calls/tcsetpgrp.c | 27 +- .../{calls/cfsetispeed.c => intrin/_getenv.c} | 32 +- libc/intrin/_getenv.internal.h | 15 + libc/intrin/bt.c | 27 +- libc/{runtime => intrin}/clearenv.c | 4 +- libc/intrin/describeflock.c | 2 +- libc/intrin/describesigaction.c | 55 ++- libc/intrin/describesigset.c | 14 +- libc/intrin/extend.c | 33 +- .../mem/putenv_test.c => libc/intrin/getenv.c | 36 +- libc/intrin/intrin.mk | 3 +- libc/intrin/kmalloc.c | 42 +- libc/intrin/kmalloc.h | 2 +- libc/{mem => intrin}/putenv.c | 122 ++--- libc/{mem => intrin}/setenv.c | 21 +- libc/intrin/sigaddset.c | 11 +- libc/intrin/sigcountset.c | 22 +- libc/intrin/sigdelset.c | 1 + libc/intrin/sigfillset.c | 15 +- libc/intrin/sigismember.c | 7 +- libc/intrin/sigisprecious.c | 6 +- libc/intrin/sigisprecious.inc | 3 + libc/intrin/strsignal_r.c | 1 + libc/{calls => intrin}/unsetenv.c | 44 +- libc/log/backtrace2.c | 13 +- libc/log/backtrace3.c | 2 +- libc/log/log.mk | 1 + libc/log/showcrashreports.c | 1 + libc/log/vflogf.c | 6 +- libc/mem/internal.h | 1 - libc/mem/sortedints.c | 93 ++++ libc/mem/sortedints.internal.h | 19 + libc/nexgen32e/longjmp.S | 6 +- .../cfgetospeed.c => nexgen32e/siglongjmp.S} | 25 +- libc/nt/comms.h | 36 ++ libc/nt/kernel32/ClearCommBreak.s | 13 + libc/nt/kernel32/PurgeComm.s | 10 + libc/nt/kernel32/TransmitCommChar.s | 10 + libc/nt/master.sh | 6 +- libc/runtime/cocmd.c | 45 +- libc/runtime/enable_threads.c | 7 +- libc/runtime/enable_tls.c | 7 +- libc/runtime/fork-nt.c | 14 +- .../cfgetispeed.c => runtime/fork-sysv.c} | 22 +- libc/runtime/fork.c | 23 +- libc/runtime/hook.greg.c | 7 +- libc/runtime/memtrack.internal.h | 36 +- libc/runtime/morph.greg.c | 71 ++- libc/runtime/morph.h | 12 + libc/runtime/runtime.h | 4 +- libc/runtime/straceinit.greg.c | 4 +- libc/runtime/vfork.S | 94 ++-- libc/sock/socketpair-nt.c | 4 +- libc/stdio/getrandom.c | 3 +- libc/str/str.h | 3 + libc/sysv/calls/__sys_fork.s | 2 + libc/sysv/calls/sys_fork.s | 2 - libc/sysv/consts.sh | 74 +-- libc/sysv/consts/B1000000.s | 2 +- libc/sysv/consts/B110.s | 2 +- libc/sysv/consts/B115200.s | 2 +- libc/sysv/consts/B1152000.s | 2 +- libc/sysv/consts/B1200.s | 2 +- libc/sysv/consts/B134.s | 2 +- libc/sysv/consts/B150.s | 2 +- libc/sysv/consts/B1500000.s | 2 +- libc/sysv/consts/B1800.s | 2 +- libc/sysv/consts/B19200.s | 2 +- libc/sysv/consts/B200.s | 2 +- libc/sysv/consts/B2000000.s | 2 +- libc/sysv/consts/B230400.s | 2 +- libc/sysv/consts/B2400.s | 2 +- libc/sysv/consts/B2500000.s | 2 +- libc/sysv/consts/B300.s | 2 +- libc/sysv/consts/B3000000.s | 2 +- libc/sysv/consts/B3500000.s | 2 +- libc/sysv/consts/B38400.s | 2 +- libc/sysv/consts/B4000000.s | 2 +- libc/sysv/consts/B4800.s | 2 +- libc/sysv/consts/B50.s | 2 +- libc/sysv/consts/B500000.s | 2 +- libc/sysv/consts/B57600.s | 2 +- libc/sysv/consts/B576000.s | 2 +- libc/sysv/consts/B600.s | 2 +- libc/sysv/consts/B75.s | 2 +- libc/sysv/consts/B9600.s | 2 +- libc/sysv/consts/F_OFD_GETLK.s | 2 +- libc/sysv/consts/F_OFD_SETLK.s | 2 +- libc/sysv/consts/F_OFD_SETLKW.s | 2 +- libc/sysv/consts/TIOCDRAIN.s | 2 - libc/sysv/consts/clone.h | 10 +- libc/sysv/consts/f.h | 2 +- libc/sysv/consts/termios.h | 2 - libc/sysv/syscalls.sh | 2 +- libc/thread/freebsd.internal.h | 3 + libc/thread/posixthread.internal.h | 4 +- libc/thread/pthread_attr_getscope.c | 9 +- libc/thread/pthread_attr_setscope.c | 31 +- libc/thread/pthread_cleanup_pop.c | 5 +- libc/thread/pthread_cleanup_push.c | 2 +- libc/thread/pthread_create.c | 11 +- libc/thread/pthread_kill.c | 1 + libc/thread/pthread_mutexattr_gettype.c | 1 - libc/thread/pthread_self.c | 1 + libc/thread/pthread_sigmask.c | 1 + libc/thread/thread.h | 5 +- libc/time/iso8601.c | 41 ++ libc/time/iso8601us.c | 80 ++++ libc/time/struct/tm.h | 1 + libc/time/time.mk | 5 +- libc/time/xiso8601.c | 4 +- libc/{intrin/intrin.h => zipos/.cosmo} | 0 libc/zipos/open.c | 5 +- libc/zipos/zipos.S | 11 +- libc/zipos/zipos.mk | 7 +- net/http/http.h | 1 + net/http/http.mk | 2 + .../build/lib => net/http}/isnocompressext.c | 9 +- net/turfwar/.init.lua | 46 ++ .../getenv.greg.c => net/turfwar/blackhole.c | 93 ++-- net/turfwar/blackholed.c | 448 ++++++++++++++++++ net/turfwar/blackholed.sh | 5 + net/turfwar/turfwar.c | 57 ++- net/turfwar/turfwar.mk | 28 +- test/libc/calls/access_test.c | 2 +- test/libc/calls/chdir_test.c | 2 +- test/libc/calls/lock2_test.c | 168 +++++++ test/libc/calls/lock_ofd_test.c | 207 ++++++++ test/libc/calls/lock_test.c | 2 +- test/libc/calls/open_test.c | 2 +- test/libc/calls/sigaction_test.c | 25 + test/libc/calls/test.mk | 6 + test/libc/calls/unlinkat_test.c | 2 +- test/libc/intrin/describesigset_test.c | 9 +- test/libc/intrin/lockipc_test.c | 1 - test/libc/intrin/lockscale_test.c | 1 - test/libc/intrin/pthread_atfork_test.c | 1 - test/libc/intrin/putenv_test.c | 69 +++ test/libc/log/backtrace_test.c | 1 + test/libc/mem/sortedints_test.c | 126 +++++ test/libc/stdio/dirstream_test.c | 4 + test/libc/thread/pthread_cond_signal_test.c | 4 +- test/libc/thread/pthread_rwlock_rdlock_test.c | 2 +- test/libc/time/iso8601_test.c | 51 ++ .../lib => net/http}/isnocompressext_test.c | 5 +- test/tool/net/redbean_test.c | 107 ++++- test/tool/net/sqlite_test.c | 1 - test/tool/net/test.mk | 22 +- third_party/python/Python/sysmodule.c | 2 +- third_party/sqlite3/README.cosmo | 6 +- third_party/sqlite3/main.shell.c | 1 + third_party/sqlite3/shell.c | 14 + third_party/sqlite3/sqlite3.mk | 1 + third_party/zip/fileio.c | 82 ---- tool/build/lib/buildlib.mk | 1 + tool/build/lib/elfwriter_zip.c | 4 +- tool/build/lib/isnocompressext.h | 10 - tool/net/counters.inc | 4 + tool/net/demo/.init.lua | 3 + tool/net/fetch.inc | 11 +- tool/net/help.txt | 154 ++++++ tool/net/redbean.c | 300 ++++++++++-- 209 files changed, 3818 insertions(+), 998 deletions(-) create mode 100644 examples/symtab.c create mode 100644 libc/calls/blocksigs.internal.h create mode 100644 libc/calls/cfspeed.c rename libc/{intrin => calls}/releasefd.c (81%) rename libc/{intrin/releasefd_unlocked.c => calls/sigblockall.c} (87%) rename libc/calls/{sigprocmask-sysv.greg.c => sigprocmask-sysv.c} (100%) rename libc/calls/{cfsetospeed.c => sigsetmask.c} (75%) rename libc/{calls/cfsetispeed.c => intrin/_getenv.c} (74%) create mode 100644 libc/intrin/_getenv.internal.h rename libc/{runtime => intrin}/clearenv.c (96%) rename test/libc/mem/putenv_test.c => libc/intrin/getenv.c (72%) rename libc/{mem => intrin}/putenv.c (60%) rename libc/{mem => intrin}/setenv.c (85%) create mode 100644 libc/intrin/sigisprecious.inc rename libc/{calls => intrin}/unsetenv.c (75%) create mode 100644 libc/mem/sortedints.c create mode 100644 libc/mem/sortedints.internal.h rename libc/{calls/cfgetospeed.c => nexgen32e/siglongjmp.S} (77%) create mode 100644 libc/nt/comms.h rename libc/{calls/cfgetispeed.c => runtime/fork-sysv.c} (82%) create mode 100644 libc/runtime/morph.h create mode 100644 libc/sysv/calls/__sys_fork.s delete mode 100644 libc/sysv/calls/sys_fork.s delete mode 100644 libc/sysv/consts/TIOCDRAIN.s create mode 100644 libc/time/iso8601us.c rename libc/{intrin/intrin.h => zipos/.cosmo} (100%) mode change 100755 => 100644 rename {tool/build/lib => net/http}/isnocompressext.c (94%) create mode 100644 net/turfwar/.init.lua rename libc/intrin/getenv.greg.c => net/turfwar/blackhole.c (59%) create mode 100644 net/turfwar/blackholed.c create mode 100755 net/turfwar/blackholed.sh create mode 100644 test/libc/calls/lock2_test.c create mode 100644 test/libc/calls/lock_ofd_test.c create mode 100644 test/libc/intrin/putenv_test.c create mode 100644 test/libc/mem/sortedints_test.c create mode 100644 test/libc/time/iso8601_test.c rename test/{tool/build/lib => net/http}/isnocompressext_test.c (93%) delete mode 100644 tool/build/lib/isnocompressext.h diff --git a/ape/ape.S b/ape/ape.S index 5313039db..2521ac30c 100644 --- a/ape/ape.S +++ b/ape/ape.S @@ -112,7 +112,15 @@ cstr: .endobj cstr,globl,hidden # ←for gdb readability // // @param dl is drive number // @noreturn -ape_mz: .asciz "MZqFpD='\n" # Mark 'Zibo' Joseph Zbikowski +ape_mz: +#if SupportsWindows() || SupportsMetal() + .asciz "MZqFpD='\n" # Mark 'Zibo' Joseph Zbikowski +#else +// Avoid virus scanner reputation damage when targeting System Five. +// WARNING: This prefix is experimental; it may be removed sometime. +// TODO(jart): Find another prefix that will work with BIOS loading. + .asciz "JTqFpD='\n" # Mark 'Zibo' Joseph Zbikowski +#endif .short 0x1000 # MZ: lowers upper bound load / 16 .short 0xf800 # MZ: roll greed on bss .short 0 # MZ: lower bound on stack segment diff --git a/ape/apeinstall.sh b/ape/apeinstall.sh index 4f1c50948..c97d8fff7 100755 --- a/ape/apeinstall.sh +++ b/ape/apeinstall.sh @@ -1,6 +1,6 @@ #!/bin/sh -if [ $UID -eq 0 ]; then +if [ "$(id -u)" -eq 0 ]; then SUDO= else SUDO=sudo @@ -97,6 +97,8 @@ if [ x"$(uname -s)" = xLinux ]; then echo you may need to edit configs to persist across reboot >&2 echo '$SUDO sh -c "echo '"'"':APE:M::MZqFpD::/usr/bin/ape:'"'"' >/proc/sys/fs/binfmt_misc/register"' >&2 $SUDO sh -c "echo ':APE:M::MZqFpD::/usr/bin/ape:' >/proc/sys/fs/binfmt_misc/register" || exit + echo '$SUDO sh -c "echo '"'"':APE-sysv:M::JTqFpD::/usr/bin/ape:'"'"' >/proc/sys/fs/binfmt_misc/register"' >&2 + $SUDO sh -c "echo ':APE-sysv:M::JTqFpD::/usr/bin/ape:' >/proc/sys/fs/binfmt_misc/register" || exit echo done >&2 if [ x"$(cat /proc/sys/fs/binfmt_misc/status)" = xdisabled ]; then diff --git a/ape/apeuninstall.sh b/ape/apeuninstall.sh index e4abe5406..69cd41031 100755 --- a/ape/apeuninstall.sh +++ b/ape/apeuninstall.sh @@ -11,6 +11,7 @@ fi echo "APE Uninstaller intends to run" echo echo " $SUDO sh -c 'echo -1 >/proc/sys/fs/binfmt_misc/APE'" + echo " $SUDO sh -c 'echo -1 >/proc/sys/fs/binfmt_misc/APE-sysv'" echo " $SUDO rm -f /usr/bin/ape ~/.ape o/tmp/.ape /tmp/.ape" echo echo "You may then use ape/apeinstall.sh to reinstall it" @@ -21,4 +22,7 @@ set -ex if [ -f /proc/sys/fs/binfmt_misc/APE ]; then $SUDO sh -c 'echo -1 >/proc/sys/fs/binfmt_misc/APE' || exit fi +if [ -f /proc/sys/fs/binfmt_misc/APE-sysv ]; then + $SUDO sh -c 'echo -1 >/proc/sys/fs/binfmt_misc/APE-sysv' || exit +fi $SUDO rm -f /usr/bin/ape ~/.ape o/tmp/.ape o/tmp/ape /tmp/.ape /tmp/ape || exit diff --git a/build/bootstrap/make.com b/build/bootstrap/make.com index a9ded9870bd25edda743d842d878af3d8b2826b9..04ddfe9831c2617c7b8c038f2877fab9d7fd1510 100755 GIT binary patch delta 205741 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

RYXq}>0&xSS;?dT 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

ViHzDq6gojEV47oFxeZJN_ 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

LCi}^-l&(qBRgE+*|9n-GAuH3c= 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 IA_GGG1)(Y)5&!@I delta 201608 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 diff --git a/build/config.mk b/build/config.mk index d37681ec8..3cf604303 100644 --- a/build/config.mk +++ b/build/config.mk @@ -29,7 +29,7 @@ endif # ifeq ($(MODE),fastbuild) CONFIG_CCFLAGS += $(BACKTRACES) $(FTRACE) -O -CONFIG_CPPFLAGS += -DSYSDEBUG +CONFIG_CPPFLAGS += -DSYSDEBUG -DDWARFLESS CONFIG_OFLAGS += -g0 CONFIG_LDFLAGS += -S TARGET_ARCH ?= -msse3 @@ -125,6 +125,22 @@ TARGET_ARCH ?= -msse3 OVERRIDE_CCFLAGS += -fno-pie endif +# System Five Mode +# +# - `make MODE=sysv` +# - Optimized +# - Backtraces +# - Debuggable +# - Syscall tracing +# - Function tracing +# - No Windows bloat! +# +ifeq ($(MODE), sysv) +CONFIG_CCFLAGS += $(BACKTRACES) $(FTRACE) -O2 +CONFIG_CPPFLAGS += -DSYSDEBUG -DSUPPORT_VECTOR=121 +TARGET_ARCH ?= -msse3 +endif + # Tiny Mode # # - `make MODE=tiny` @@ -151,7 +167,8 @@ CONFIG_CCFLAGS += \ -fschedule-insns2 \ -fomit-frame-pointer \ -momit-leaf-frame-pointer \ - -foptimize-sibling-calls + -foptimize-sibling-calls \ + -DDWARFLESS CONFIG_OFLAGS += \ -g0 CONFIG_LDFLAGS += \ @@ -181,7 +198,8 @@ CONFIG_CPPFLAGS += \ -DTINY \ -DNDEBUG \ -DTRUSTWORTHY \ - -DSUPPORT_VECTOR=1 + -DSUPPORT_VECTOR=1 \ + -DDWARFLESS DEFAULT_COPTS += \ -mred-zone CONFIG_OFLAGS += \ @@ -217,7 +235,8 @@ CONFIG_CPPFLAGS += \ -DTINY \ -DNDEBUG \ -DTRUSTWORTHY \ - -DSUPPORT_VECTOR=113 + -DSUPPORT_VECTOR=113 \ + -DDWARFLESS DEFAULT_COPTS += \ -mred-zone CONFIG_OFLAGS += \ @@ -252,7 +271,8 @@ CONFIG_CPPFLAGS += \ -DTINY \ -DNDEBUG \ -DTRUSTWORTHY \ - -DSUPPORT_VECTOR=121 + -DSUPPORT_VECTOR=121 \ + -DDWARFLESS DEFAULT_COPTS += \ -mred-zone CONFIG_CCFLAGS += \ @@ -287,7 +307,8 @@ CONFIG_CPPFLAGS += \ -DTINY \ -DNDEBUG \ -DTRUSTWORTHY \ - -DSUPPORT_VECTOR=251 + -DSUPPORT_VECTOR=251 \ + -DDWARFLESS CONFIG_CCFLAGS += \ -Os \ -fno-align-functions \ diff --git a/examples/auto-memory-safety-crash2.c b/examples/auto-memory-safety-crash2.c index 3365afd0b..5916e0b40 100644 --- a/examples/auto-memory-safety-crash2.c +++ b/examples/auto-memory-safety-crash2.c @@ -7,12 +7,12 @@ │ • http://creativecommons.org/publicdomain/zero/1.0/ │ ╚─────────────────────────────────────────────────────────────────*/ #endif -#include "libc/intrin/bits.h" #include "libc/dce.h" +#include "libc/intrin/bits.h" +#include "libc/intrin/kprintf.h" #include "libc/log/log.h" #include "libc/mem/mem.h" #include "libc/runtime/runtime.h" -#include "libc/stdio/stdio.h" #include "libc/str/str.h" /** @@ -59,9 +59,15 @@ int main(int argc, char *argv[]) { if (!IsAsan()) { - printf("this example is intended for MODE=asan or MODE=dbg\n"); + kprintf("this example is intended for MODE=asan or MODE=dbg\n"); exit(1); } + + kprintf("----------------\n"); + kprintf(" THIS IS A TEST \n"); + kprintf("SIMULATING CRASH\n"); + kprintf("----------------\n"); + char *buffer; ShowCrashReports(); /* not needed but yoinks appropriate symbols */ buffer = malloc(13); diff --git a/examples/crashreport.c b/examples/crashreport.c index 558b6a28a..326356f74 100644 --- a/examples/crashreport.c +++ b/examples/crashreport.c @@ -7,6 +7,7 @@ │ • http://creativecommons.org/publicdomain/zero/1.0/ │ ╚─────────────────────────────────────────────────────────────────*/ #endif +#include "libc/intrin/kprintf.h" #include "libc/log/log.h" #include "libc/runtime/symbols.internal.h" @@ -24,6 +25,12 @@ */ noubsan int main(int argc, char *argv[]) { + + kprintf("----------------\n"); + kprintf(" THIS IS A TEST \n"); + kprintf("SIMULATING CRASH\n"); + kprintf("----------------\n"); + volatile int64_t x; ShowCrashReports(); return 1 / (x = 0); diff --git a/examples/examples.mk b/examples/examples.mk index 944c677a4..26317f1e8 100644 --- a/examples/examples.mk +++ b/examples/examples.mk @@ -146,6 +146,25 @@ o/$(MODE)/examples/nesemu1.com.dbg: \ $(EXAMPLES_BOOTLOADER) @$(APELINK) +# # force symtab.com to be a zip file, by pulling a zip asset into linkage +# # we wouldn't need to do this if we depended on functions like localtime +# o/$(MODE)/examples/symtab.com.dbg: \ +# $(EXAMPLES_DEPS) \ +# o/$(MODE)/examples/symtab.o \ +# o/$(MODE)/examples/symtab.c.zip.o \ +# o/$(MODE)/examples/examples.pkg \ +# $(EXAMPLES_BOOTLOADER) +# @$(APELINK) + +# modify .com so it can read the symbol table without needing the .com.dbg file +o/$(MODE)/examples/symtab.com: \ + o/$(MODE)/examples/symtab.com.dbg \ + o/$(MODE)/third_party/zip/zip.com \ + o/$(MODE)/tool/build/symtab.com + @$(MAKE_OBJCOPY) + @$(MAKE_SYMTAB_CREATE) + @$(MAKE_SYMTAB_ZIP) + o/$(MODE)/examples/picol.o: private \ OVERRIDE_CPPFLAGS += \ -DSTACK_FRAME_UNLIMITED diff --git a/examples/nesemu1.cc b/examples/nesemu1.cc index 2e648dbea..099fbe79d 100644 --- a/examples/nesemu1.cc +++ b/examples/nesemu1.cc @@ -47,7 +47,7 @@ #include "third_party/libcxx/vector" #include "tool/viz/lib/knobs.h" -STATIC_YOINK("zip_uri_support"); +STATIC_YOINK("zipos"); #define USAGE \ " [ROM] [FMV]\n\ diff --git a/examples/symtab.c b/examples/symtab.c new file mode 100644 index 000000000..2c11eb964 --- /dev/null +++ b/examples/symtab.c @@ -0,0 +1,42 @@ +#if 0 +/*─────────────────────────────────────────────────────────────────╗ +│ To the extent possible under law, Justine Tunney has waived │ +│ all copyright and related or neighboring rights to this file, │ +│ as it is written in the following disclaimers: │ +│ • http://unlicense.org/ │ +│ • http://creativecommons.org/publicdomain/zero/1.0/ │ +╚─────────────────────────────────────────────────────────────────*/ +#endif +#include "libc/intrin/kprintf.h" +#include "libc/log/log.h" + +/** + * @fileoverview example of how to embed symbol table in .com file + * + * # build our binary + * make -j16 o//examples/symtab.com + * + * # move binary somewhere else + * # so it can't find the .com.dbg binary + * cp o//examples/symtab.com /tmp + * + * # run program + * # notice that it has a symbolic backtrace + * /tmp/symtab.com + * + * @see examples/examples.mk + */ + +int main(int argc, char *argv[]) { + + // this links all the debugging and zip functionality + ShowCrashReports(); + + kprintf("----------------\n"); + kprintf(" THIS IS A TEST \n"); + kprintf("SIMULATING CRASH\n"); + kprintf("----------------\n"); + + volatile int64_t x; + return 1 / (x = 0); +} diff --git a/libc/calls/__sig_mask.c b/libc/calls/__sig_mask.c index 2cb92a38f..1fc839bd5 100644 --- a/libc/calls/__sig_mask.c +++ b/libc/calls/__sig_mask.c @@ -16,7 +16,9 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" #include "libc/calls/sig.internal.h" +#include "libc/calls/struct/sigset.h" #include "libc/sysv/consts/sig.h" #include "libc/sysv/errfuns.h" #include "libc/thread/tls.h" diff --git a/libc/calls/blocksigs.internal.h b/libc/calls/blocksigs.internal.h new file mode 100644 index 000000000..1886f02c1 --- /dev/null +++ b/libc/calls/blocksigs.internal.h @@ -0,0 +1,19 @@ +#ifndef COSMOPOLITAN_LIBC_CALLS_BLOCKSIGS_INTERNAL_H_ +#define COSMOPOLITAN_LIBC_CALLS_BLOCKSIGS_INTERNAL_H_ +#include "libc/calls/struct/sigset.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +#define BLOCK_SIGNALS \ + do { \ + sigset_t _SigMask; \ + _SigMask = _sigblockall() + +#define ALLOW_SIGNALS \ + _sigsetmask(_SigMask); \ + } \ + while (0) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_CALLS_BLOCKSIGS_INTERNAL_H_ */ diff --git a/libc/calls/calls.h b/libc/calls/calls.h index df2bceef7..7ee4d2083 100644 --- a/libc/calls/calls.h +++ b/libc/calls/calls.h @@ -65,13 +65,13 @@ char *ttyname(int); int access(const char *, int) dontthrow; int arch_prctl(); int chdir(const char *); -int chmod(const char *, uint32_t); -int chown(const char *, uint32_t, uint32_t); +int chmod(const char *, unsigned); +int chown(const char *, unsigned, unsigned); int chroot(const char *); int close(int); int close_range(unsigned, unsigned, unsigned); int closefrom(int); -int creat(const char *, uint32_t); +int creat(const char *, unsigned); int dup(int); int dup2(int, int); int dup3(int, int, int); @@ -84,25 +84,22 @@ int execv(const char *, char *const[]); int execve(const char *, char *const[], char *const[]); int execvp(const char *, char *const[]); int execvpe(const char *, char *const[], char *const[]); -int fexecve(int, char *const[], char *const[]); int faccessat(int, const char *, int, int); int fadvise(int, uint64_t, uint64_t, int); int fchdir(int); -int fchmod(int, uint32_t) dontthrow; -int fchmodat(int, const char *, uint32_t, int); -int fchown(int, uint32_t, uint32_t); -int fchownat(int, const char *, uint32_t, uint32_t, int); +int fchmod(int, unsigned) dontthrow; +int fchmodat(int, const char *, unsigned, int); +int fchown(int, unsigned, unsigned); +int fchownat(int, const char *, unsigned, unsigned, int); int fcntl(int, int, ...); int fdatasync(int); +int fexecve(int, char *const[], char *const[]); int flock(int, int); int fork(void); int fsync(int); int ftruncate(int, int64_t); int getdomainname(char *, size_t); -uint32_t getegid(void) nosideeffect; -uint32_t geteuid(void) nosideeffect; -uint32_t getgid(void) nosideeffect; -int getgroups(int size, uint32_t list[]); +int getgroups(int, unsigned[]); int gethostname(char *, size_t); int getloadavg(double *, int); int getpgid(int) libcesque; @@ -110,36 +107,29 @@ int getpgrp(void) nosideeffect; int getpid(void) nosideeffect libcesque; int getppid(void); int getpriority(int, unsigned); -int getresgid(uint32_t *, uint32_t *, uint32_t *); -int getresuid(uint32_t *, uint32_t *, uint32_t *); +int getresgid(unsigned *, unsigned *, unsigned *); +int getresuid(unsigned *, unsigned *, unsigned *); int getsid(int) nosideeffect libcesque; int gettid(void) libcesque; -uint32_t getuid(void) libcesque; -int sys_iopl(int); int ioprio_get(int, int); int ioprio_set(int, int, int); int issetugid(void); int kill(int, int); int killpg(int, int); -int lchmod(const char *, uint32_t); -int lchown(const char *, uint32_t, uint32_t); +int lchmod(const char *, unsigned); +int lchown(const char *, unsigned, unsigned); int link(const char *, const char *) dontthrow; int linkat(int, const char *, int, const char *, int); int madvise(void *, uint64_t, int); +int makedirs(const char *, unsigned); int memfd_create(const char *, unsigned int); int mincore(void *, size_t, unsigned char *); int mkdir(const char *, unsigned); int mkdirat(int, const char *, unsigned); -int makedirs(const char *, unsigned); -int mkfifo(const char *, uint32_t); -int mkfifoat(int, const char *, uint32_t); -int mknod(const char *, uint32_t, uint64_t); -int mknodat(int, const char *, int32_t, uint64_t); -int sys_mlock(const void *, size_t); -int sys_mlock2(const void *, size_t, int); -int sys_mlockall(int); -int sys_munlock(const void *, size_t); -int sys_munlockall(void); +int mkfifo(const char *, unsigned); +int mkfifoat(int, const char *, unsigned); +int mknod(const char *, unsigned, uint64_t); +int mknodat(int, const char *, int, uint64_t); int nice(int); int open(const char *, int, ...); int openat(int, const char *, int, ...); @@ -161,19 +151,19 @@ int renameat2(long, const char *, long, const char *, int); int rmdir(const char *); int sched_yield(void); int seccomp(unsigned, unsigned, void *); -int setegid(uint32_t); -int seteuid(uint32_t); +int setegid(unsigned); +int seteuid(unsigned); int setfsgid(unsigned); int setfsuid(unsigned); int setgid(unsigned); -int setgroups(size_t, const uint32_t[]); +int setgroups(size_t, const unsigned[]); int setpgid(int, int); int setpgrp(void); int setpriority(int, unsigned, int); -int setregid(uint32_t, uint32_t); -int setresgid(uint32_t, uint32_t, uint32_t); -int setresuid(uint32_t, uint32_t, uint32_t); -int setreuid(uint32_t, uint32_t); +int setregid(unsigned, unsigned); +int setresgid(unsigned, unsigned, unsigned); +int setresuid(unsigned, unsigned, unsigned); +int setreuid(unsigned, unsigned); int setsid(void); int setuid(unsigned); int sigignore(int); @@ -181,16 +171,22 @@ int siginterrupt(int, int); int symlink(const char *, const char *); int symlinkat(const char *, int, const char *); int sync_file_range(int, int64_t, int64_t, unsigned); +int sys_iopl(int); +int sys_mlock(const void *, size_t); +int sys_mlock2(const void *, size_t, int); +int sys_mlockall(int); +int sys_munlock(const void *, size_t); +int sys_munlockall(void); int sys_ptrace(int, ...); int sys_sysctl(const int *, unsigned, void *, size_t *, void *, size_t); -int tcsetpgrp(int, int32_t); +int tcgetpgrp(int); +int tcsetpgrp(int, int); int tgkill(int, int, int); int tkill(int, int); int tmpfd(void); -int touch(const char *, uint32_t); +int touch(const char *, unsigned); int truncate(const char *, int64_t); int ttyname_r(int, char *, size_t); -unsigned umask(unsigned); int unlink(const char *); int unlink_s(const char **); int unlinkat(int, const char *, int); @@ -199,11 +195,10 @@ int usleep(unsigned); int vfork(void) returnstwice; int wait(int *); int waitpid(int, int *, int); -int32_t tcgetpgrp(int); intptr_t syscall(int, ...); long ptrace(int, ...); -ssize_t copy_file_range(int, long *, int, long *, size_t, uint32_t); -ssize_t copyfd(int, int64_t *, int, int64_t *, size_t, uint32_t); +ssize_t copy_file_range(int, long *, int, long *, size_t, unsigned); +ssize_t copyfd(int, int64_t *, int, int64_t *, size_t, unsigned); ssize_t getfiledescriptorsize(int); ssize_t lseek(int, int64_t, int); ssize_t pread(int, void *, size_t, int64_t); @@ -212,8 +207,13 @@ ssize_t read(int, void *, size_t); ssize_t readansi(int, char *, size_t); ssize_t readlink(const char *, char *, size_t); ssize_t readlinkat(int, const char *, char *, size_t); -ssize_t splice(int, int64_t *, int, int64_t *, size_t, uint32_t); +ssize_t splice(int, int64_t *, int, int64_t *, size_t, unsigned); ssize_t write(int, const void *, size_t); +unsigned getegid(void) nosideeffect; +unsigned geteuid(void) nosideeffect; +unsigned getgid(void) nosideeffect; +unsigned getuid(void) libcesque; +unsigned umask(unsigned); void sync(void); COSMOPOLITAN_C_END_ diff --git a/libc/calls/cfspeed.c b/libc/calls/cfspeed.c new file mode 100644 index 000000000..e6a388a1d --- /dev/null +++ b/libc/calls/cfspeed.c @@ -0,0 +1,88 @@ +/*-*- 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/calls/termios.h" +#include "libc/sysv/consts/termios.h" +#include "libc/sysv/errfuns.h" + +/** + * Returns input baud rate. + * @asyncsignalsafe + */ +uint32_t cfgetispeed(const struct termios *t) { + if (CBAUD) { + return t->c_cflag & CBAUD; + } else { + return t->c_ispeed; + } +} + +/** + * Returns output baud rate. + * @asyncsignalsafe + */ +uint32_t cfgetospeed(const struct termios *t) { + if (CBAUD) { + return t->c_cflag & CBAUD; + } else { + return t->c_ospeed; + } +} + +/** + * Sets input baud rate. + * + * @return 0 on success, or -1 w/ errno + * @asyncsignalsafe + */ +int cfsetispeed(struct termios *t, unsigned speed) { + if (speed) { + if (CBAUD) { + if (!(speed & ~CBAUD)) { + t->c_cflag &= ~CBAUD; + t->c_cflag |= speed; + } else { + return einval(); + } + } else { + t->c_ispeed = speed; + } + } + return 0; +} + +/** + * Sets output baud rate. + * + * @return 0 on success, or -1 w/ errno + * @asyncsignalsafe + */ +int cfsetospeed(struct termios *t, unsigned speed) { + if (CBAUD) { + if (!(speed & ~CBAUD)) { + t->c_cflag &= ~CBAUD; + t->c_cflag |= speed; + return 0; + } else { + return einval(); + } + } else { + t->c_ospeed = speed; + return 0; + } +} diff --git a/libc/calls/close-nt.c b/libc/calls/close-nt.c index 0db63759d..af398658d 100644 --- a/libc/calls/close-nt.c +++ b/libc/calls/close-nt.c @@ -18,15 +18,22 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/struct/fd.internal.h" #include "libc/errno.h" +#include "libc/intrin/weaken.h" #include "libc/nt/enum/filetype.h" #include "libc/nt/files.h" #include "libc/nt/runtime.h" #include "libc/sysv/consts/o.h" -textwindows int sys_close_nt(struct Fd *fd) { +void sys_fcntl_nt_lock_cleanup(int) hidden; + +textwindows int sys_close_nt(struct Fd *fd, int fildes) { int e; bool ok = true; + if (_weaken(sys_fcntl_nt_lock_cleanup)) { + _weaken(sys_fcntl_nt_lock_cleanup)(fildes); + } + if (fd->kind == kFdFile && ((fd->flags & O_ACCMODE) != O_RDONLY && GetFileType(fd->handle) == kNtFileTypeDisk)) { // Like Linux, closing a file on Windows doesn't guarantee it's diff --git a/libc/calls/close.c b/libc/calls/close.c index 4aa2dbca8..d3d30fc3a 100644 --- a/libc/calls/close.c +++ b/libc/calls/close.c @@ -16,10 +16,10 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/assert.h" #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/calls/state.internal.h" +#include "libc/calls/struct/fd.internal.h" #include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" @@ -81,7 +81,7 @@ int close(int fd) { } else if (__isfdkind(fd, kFdFile) || // __isfdkind(fd, kFdConsole) || // __isfdkind(fd, kFdProcess)) { // - rc = sys_close_nt(g_fds.p + fd); + rc = sys_close_nt(g_fds.p + fd, fd); } else { rc = eio(); } diff --git a/libc/calls/closefrom.c b/libc/calls/closefrom.c index 566040ade..759a26351 100644 --- a/libc/calls/closefrom.c +++ b/libc/calls/closefrom.c @@ -27,12 +27,9 @@ /** * Closes extra file descriptors, e.g. * - * // close all non-stdio file descriptors - * if (closefrom(3) == -1) { - * for (int i = 3; i < 256; ++i) { + * if (closefrom(3)) + * for (int i = 3; i < 256; ++i) * close(i); - * } - * } * * @return 0 on success, or -1 w/ errno * @raise EBADF if `first` is negative diff --git a/libc/calls/dup-nt.c b/libc/calls/dup-nt.c index 40b094385..d33ea0312 100644 --- a/libc/calls/dup-nt.c +++ b/libc/calls/dup-nt.c @@ -45,27 +45,18 @@ textwindows int sys_dup_nt(int oldfd, int newfd, int flags, int start) { } // allocate a new file descriptor - for (;;) { - if (newfd == -1) { - if ((newfd = __reservefd_unlocked(start)) == -1) { - __fds_unlock(); - return -1; - } - break; - } else { - if (__ensurefds_unlocked(newfd) == -1) { - __fds_unlock(); - return -1; - } - if (g_fds.p[newfd].kind) { - __fds_unlock(); - close(newfd); - __fds_lock(); - } - if (!g_fds.p[newfd].kind) { - g_fds.p[newfd].kind = kFdReserved; - break; - } + if (newfd == -1) { + if ((newfd = __reservefd_unlocked(start)) == -1) { + __fds_unlock(); + return -1; + } + } else { + if (__ensurefds_unlocked(newfd) == -1) { + __fds_unlock(); + return -1; + } + if (g_fds.p[newfd].kind) { + sys_close_nt(g_fds.p + newfd, newfd); } } @@ -86,7 +77,7 @@ textwindows int sys_dup_nt(int oldfd, int newfd, int flags, int start) { } rc = newfd; } else { - __releasefd_unlocked(newfd); + __releasefd(newfd); rc = __winerr(); } diff --git a/libc/calls/execve-sysv.c b/libc/calls/execve-sysv.c index 38add6742..cf275a93c 100644 --- a/libc/calls/execve-sysv.c +++ b/libc/calls/execve-sysv.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/blocksigs.internal.h" #include "libc/calls/calls.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" @@ -40,7 +41,9 @@ static bool IsApeBinary(const char *path) { char buf[8]; bool res = false; if ((fd = sys_open(path, O_RDONLY, 0)) != -1) { - if (sys_read(fd, buf, 8) == 8 && READ64LE(buf) == READ64LE("MZqFpD='")) { + if (sys_read(fd, buf, 8) == 8 && // + (READ64LE(buf) == READ64LE("MZqFpD='") || + READ64LE(buf) == READ64LE("JTqFpD='"))) { res = true; } sys_close(fd); @@ -61,35 +64,41 @@ static const char *Join(const char *a, const char *b, char buf[PATH_MAX]) { } int sys_execve(const char *prog, char *const argv[], char *const envp[]) { - int e; size_t i; + int e, rc; char *buf; char **shargs; const char *ape; e = errno; __sys_execve(prog, argv, envp); - if (errno != ENOEXEC) return -1; - for (i = 0; argv[i];) ++i; - buf = alloca(PATH_MAX); - shargs = alloca((i + 4) * sizeof(char *)); - if (IsApeBinary(prog) && - (CanExecute((ape = "/usr/bin/ape")) || - CanExecute((ape = Join(firstnonnull(getenv("TMPDIR"), - firstnonnull(getenv("HOME"), ".")), - ".ape", buf))) || - CanExecute( - (ape = Join(firstnonnull(getenv("HOME"), "."), ".ape", buf))))) { - shargs[0] = ape; - shargs[1] = "-"; - shargs[2] = prog; - memcpy(shargs + 3, argv, (i + 1) * sizeof(char *)); - } else if (CanExecute(prog)) { - shargs[0] = _PATH_BSHELL; - shargs[1] = prog; - memcpy(shargs + 2, argv + 1, i * sizeof(char *)); + if (errno == ENOEXEC) { + for (i = 0; argv[i];) ++i; + buf = alloca(PATH_MAX); + shargs = alloca((i + 4) * sizeof(char *)); + if (IsApeBinary(prog) && + (CanExecute((ape = "/usr/bin/ape")) || + CanExecute((ape = Join(firstnonnull(getenv("TMPDIR"), + firstnonnull(getenv("HOME"), ".")), + ".ape", buf))) || + CanExecute( + (ape = Join(firstnonnull(getenv("HOME"), "."), ".ape", buf))))) { + shargs[0] = ape; + shargs[1] = "-"; + shargs[2] = prog; + memcpy(shargs + 3, argv, (i + 1) * sizeof(char *)); + errno = e; + rc = __sys_execve(shargs[0], shargs, envp); + } else if (CanExecute(prog)) { + shargs[0] = _PATH_BSHELL; + shargs[1] = prog; + memcpy(shargs + 2, argv + 1, i * sizeof(char *)); + errno = e; + rc = __sys_execve(shargs[0], shargs, envp); + } else { + rc = enoexec(); + } } else { - return enoexec(); + rc = -1; } - errno = e; - return __sys_execve(shargs[0], shargs, envp); + return rc; } diff --git a/libc/calls/execve.c b/libc/calls/execve.c index 4af61ccbd..1e0395da5 100644 --- a/libc/calls/execve.c +++ b/libc/calls/execve.c @@ -54,9 +54,9 @@ int execve(const char *prog, char *const argv[], char *const envp[]) { int rc; size_t i; if (!prog || !argv || !envp || - (IsAsan() && - (!__asan_is_valid(prog, 1) || !__asan_is_valid_strlist(argv) || - !__asan_is_valid_strlist(envp)))) { + (IsAsan() && (!__asan_is_valid(prog, 1) || // + !__asan_is_valid_strlist(argv) || // + !__asan_is_valid_strlist(envp)))) { rc = efault(); } else { STRACE("execve(%#s, %s, %s) → ...", prog, DescribeStringList(argv), diff --git a/libc/calls/fcntl-nt.c b/libc/calls/fcntl-nt.c index 491388499..f5c11ab89 100644 --- a/libc/calls/fcntl-nt.c +++ b/libc/calls/fcntl-nt.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/calls/struct/fd.internal.h" @@ -24,63 +25,131 @@ #include "libc/calls/syscall_support-nt.internal.h" #include "libc/calls/wincrash.internal.h" #include "libc/errno.h" +#include "libc/intrin/kmalloc.h" #include "libc/intrin/weaken.h" +#include "libc/limits.h" #include "libc/log/backtrace.internal.h" +#include "libc/macros.internal.h" #include "libc/nt/enum/filelockflags.h" #include "libc/nt/errors.h" #include "libc/nt/files.h" #include "libc/nt/runtime.h" #include "libc/nt/struct/byhandlefileinformation.h" #include "libc/nt/struct/overlapped.h" +#include "libc/str/str.h" #include "libc/sysv/consts/f.h" #include "libc/sysv/consts/fd.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/errfuns.h" +#include "libc/thread/thread.h" -static textwindows int sys_fcntl_nt_dupfd(int fd, int cmd, int start) { - if (start < 0) return einval(); - return sys_dup_nt(fd, -1, (cmd == F_DUPFD_CLOEXEC ? O_CLOEXEC : 0), start); +struct FileLock { + struct FileLock *next; + int64_t off; + int64_t len; + int fd; + bool exc; +}; + +struct FileLocks { + pthread_mutex_t mu; + struct FileLock *list; + struct FileLock *free; +}; + +static struct FileLocks g_locks; + +static textwindows struct FileLock *NewFileLock(void) { + struct FileLock *fl; + if (g_locks.free) { + fl = g_locks.free; + g_locks.free = fl->next; + } else { + fl = kmalloc(sizeof(*fl)); + } + bzero(fl, sizeof(*fl)); + fl->next = g_locks.list; + g_locks.list = fl; + return fl; } -static textwindows int sys_fcntl_nt_lock(struct Fd *f, int cmd, uintptr_t arg) { +static textwindows void FreeFileLock(struct FileLock *fl) { + fl->next = g_locks.free; + g_locks.free = fl; +} + +static textwindows bool OverlapsFileLock(struct FileLock *fl, int64_t off, + int64_t len) { + uint64_t BegA, EndA, BegB, EndB; + BegA = off; + EndA = off + (len - 1); + BegB = fl->off; + EndB = fl->off + (fl->len - 1); + return MAX(BegA, BegB) < MIN(EndA, EndB); +} + +static textwindows bool EncompassesFileLock(struct FileLock *fl, int64_t off, + int64_t len) { + return off <= fl->off && fl->off + fl->len <= off + len; +} + +static textwindows bool EqualsFileLock(struct FileLock *fl, int64_t off, + int64_t len) { + return fl->off == off && off + len == fl->off + fl->len; +} + +hidden textwindows void sys_fcntl_nt_lock_cleanup(int fd) { + struct FileLock *fl, *ft, **flp; + pthread_mutex_lock(&g_locks.mu); + for (flp = &g_locks.list, fl = *flp; fl;) { + if (fl->fd == fd) { + *flp = fl->next; + ft = fl->next; + FreeFileLock(fl); + fl = ft; + } else { + flp = &fl->next; + fl = *flp; + } + } + pthread_mutex_unlock(&g_locks.mu); +} + +static textwindows int sys_fcntl_nt_lock(struct Fd *f, int fd, int cmd, + uintptr_t arg) { int e; struct flock *l; uint32_t flags, err; - int64_t pos, off, len, size; - struct NtByHandleFileInformation info; - - if (!GetFileInformationByHandle(f->handle, &info)) { - return __winerr(); - } - - pos = 0; - if (!SetFilePointerEx(f->handle, 0, &pos, SEEK_CUR)) { - return __winerr(); - } + struct FileLock *fl, *ft, **flp; + int64_t pos, off, len, end, size; l = (struct flock *)arg; len = l->l_len; off = l->l_start; - size = (uint64_t)info.nFileSizeHigh << 32 | info.nFileSizeLow; switch (l->l_whence) { case SEEK_SET: break; case SEEK_CUR: - off = pos + off; + pos = 0; + if (SetFilePointerEx(f->handle, 0, &pos, SEEK_CUR)) { + off = pos + off; + } else { + return __winerr(); + } break; case SEEK_END: - off = size - off; + off = INT64_MAX - off; break; default: return einval(); } if (!len) { - len = size - off; + len = INT64_MAX - off; } - if (off < 0 || len < 0) { + if (off < 0 || len < 0 || __builtin_add_overflow(off, len, &end)) { return einval(); } @@ -89,8 +158,57 @@ static textwindows int sys_fcntl_nt_lock(struct Fd *f, int cmd, uintptr_t arg) { .Pointer = (void *)(uintptr_t)off}; if (l->l_type == F_RDLCK || l->l_type == F_WRLCK) { + + if (cmd == F_SETLK || cmd == F_SETLKW) { + // make it possible to transition read locks to write locks + for (flp = &g_locks.list, fl = *flp; fl;) { + if (fl->fd == fd) { + if (EqualsFileLock(fl, off, len)) { + if (fl->exc == l->l_type == F_WRLCK) { + // we already have this lock + return 0; + } else { + // unlock our read lock and acquire write lock below + if (UnlockFileEx(f->handle, 0, len, len >> 32, &ov)) { + *flp = fl->next; + ft = fl->next; + FreeFileLock(fl); + fl = ft; + continue; + } else { + return -1; + } + } + break; + } else if (OverlapsFileLock(fl, off, len)) { + return enotsup(); + } + } + flp = &fl->next; + fl = *flp; + } + } + + // return better information on conflicting locks if possible + if (cmd == F_GETLK) { + for (fl = g_locks.list; fl; fl = fl->next) { + if (fl->fd == fd && // + OverlapsFileLock(fl, off, len) && + (l->l_type == F_WRLCK || !fl->exc)) { + l->l_whence = SEEK_SET; + l->l_start = fl->off; + l->l_len = fl->len; + l->l_type == fl->exc ? F_WRLCK : F_RDLCK; + l->l_pid = getpid(); + return 0; + } + } + } + flags = 0; if (cmd != F_SETLKW) { + // TODO(jart): we should use expo backoff in wrapper function + // should not matter since sqlite doesn't need it flags |= kNtLockfileFailImmediately; } if (l->l_type == F_WRLCK) { @@ -101,68 +219,133 @@ static textwindows int sys_fcntl_nt_lock(struct Fd *f, int cmd, uintptr_t arg) { if (ok) { l->l_type = F_UNLCK; if (!UnlockFileEx(f->handle, 0, len, len >> 32, &ov)) { - notpossible; + return -1; } } else { l->l_pid = -1; ok = true; } + } else if (ok) { + fl = NewFileLock(); + fl->off = off; + fl->len = len; + fl->exc = l->l_type == F_WRLCK; + fl->fd = fd; } return ok ? 0 : -1; } if (l->l_type == F_UNLCK) { if (cmd == F_GETLK) return einval(); - e = errno; - if (UnlockFileEx(f->handle, 0, len, len >> 32, &ov)) { - return 0; - } else if (errno == ENOLCK) { - errno = e; - return 0; - } else { - return 0; + + // allow a big range to unlock many small ranges + for (flp = &g_locks.list, fl = *flp; fl;) { + if (fl->fd == fd && EncompassesFileLock(fl, off, len)) { + struct NtOverlapped ov = {.hEvent = f->handle, + .Pointer = (void *)(uintptr_t)fl->off}; + if (UnlockFileEx(f->handle, 0, fl->len, fl->len >> 32, &ov)) { + *flp = fl->next; + ft = fl->next; + FreeFileLock(fl); + fl = ft; + } else { + return -1; + } + } else { + flp = &fl->next; + fl = *flp; + } } + + // win32 won't let us carve up existing locks + int overlap_count = 0; + for (fl = g_locks.list; fl; fl = fl->next) { + if (fl->fd == fd && // + OverlapsFileLock(fl, off, len)) { + ++overlap_count; + } + } + + // try to handle the carving cases needed by sqlite + if (overlap_count == 1) { + for (fl = g_locks.list; fl; fl = fl->next) { + if (fl->fd == fd && // + off <= fl->off && // + off + len >= fl->off && // + off + len < fl->off + fl->len) { + // cleave left side of lock + struct NtOverlapped ov = {.hEvent = f->handle, + .Pointer = (void *)(uintptr_t)fl->off}; + if (!UnlockFileEx(f->handle, 0, fl->len, fl->len >> 32, &ov)) { + return -1; + } + fl->len = (fl->off + fl->len) - (off + len); + fl->off = off + len; + ov.Pointer = (void *)(uintptr_t)fl->off; + if (!LockFileEx(f->handle, kNtLockfileExclusiveLock, 0, fl->len, + fl->len >> 32, &ov)) { + return -1; + } + return 0; + } + } + } + + if (overlap_count) { + return enotsup(); + } + + return 0; } return einval(); } +static textwindows int sys_fcntl_nt_dupfd(int fd, int cmd, int start) { + if (start < 0) return einval(); + return sys_dup_nt(fd, -1, (cmd == F_DUPFD_CLOEXEC ? O_CLOEXEC : 0), start); +} + textwindows int sys_fcntl_nt(int fd, int cmd, uintptr_t arg) { + int rc; uint32_t flags; if (__isfdkind(fd, kFdFile) || __isfdkind(fd, kFdSocket)) { if (cmd == F_GETFL) { - return g_fds.p[fd].flags & - (O_ACCMODE | O_APPEND | O_ASYNC | O_DIRECT | O_NOATIME | - O_NONBLOCK | O_RANDOM | O_SEQUENTIAL); + rc = g_fds.p[fd].flags & + (O_ACCMODE | O_APPEND | O_ASYNC | O_DIRECT | O_NOATIME | O_NONBLOCK | + O_RANDOM | O_SEQUENTIAL); } else if (cmd == F_SETFL) { // O_APPEND doesn't appear to be tunable at cursory glance // O_NONBLOCK might require we start doing all i/o in threads // O_DSYNC / O_RSYNC / O_SYNC maybe if we fsync() everything // O_DIRECT | O_RANDOM | O_SEQUENTIAL | O_NDELAY possible but // not worth it. - return einval(); + rc = einval(); } else if (cmd == F_GETFD) { if (g_fds.p[fd].flags & O_CLOEXEC) { - return FD_CLOEXEC; + rc = FD_CLOEXEC; } else { - return 0; + rc = 0; } } else if (cmd == F_SETFD) { if (arg & FD_CLOEXEC) { g_fds.p[fd].flags |= O_CLOEXEC; - return FD_CLOEXEC; + rc = FD_CLOEXEC; } else { g_fds.p[fd].flags &= ~O_CLOEXEC; - return 0; + rc = 0; } } else if (cmd == F_SETLK || cmd == F_SETLKW || cmd == F_GETLK) { - return sys_fcntl_nt_lock(g_fds.p + fd, cmd, arg); + pthread_mutex_lock(&g_locks.mu); + rc = sys_fcntl_nt_lock(g_fds.p + fd, fd, cmd, arg); + pthread_mutex_unlock(&g_locks.mu); } else if (cmd == F_DUPFD || cmd == F_DUPFD_CLOEXEC) { - return sys_fcntl_nt_dupfd(fd, cmd, arg); + rc = sys_fcntl_nt_dupfd(fd, cmd, arg); } else { - return einval(); + rc = einval(); } } else { - return ebadf(); + rc = ebadf(); } + return rc; } diff --git a/libc/calls/fcntl.c b/libc/calls/fcntl.c index be68a3f9c..06369e9ba 100644 --- a/libc/calls/fcntl.c +++ b/libc/calls/fcntl.c @@ -31,7 +31,7 @@ #include "libc/zipos/zipos.internal.h" /** - * Does things with file descriptor, via re-imagined hourglass api, e.g. + * Does things with file descriptor, e.g. * * CHECK_NE(-1, fcntl(fd, F_SETFD, FD_CLOEXEC)); * @@ -41,12 +41,14 @@ * CHECK_GE((newfd = fcntl(oldfd, F_DUPFD, 3)), 3); * CHECK_GE((newfd = fcntl(oldfd, F_DUPFD_CLOEXEC, 3)), 3); * - * This function implements POSIX Advisory Locks, which let independent - * processes (and on Windows, threads too!) read/write lock byte ranges - * of files. See `test/libc/calls/lock_test.c` for an example. + * This function implements file record locking, which lets independent + * processes (and on Linux 3.15+, threads too!) lock arbitrary ranges + * associated with a file. See `test/libc/calls/lock_test.c` and other + * locking related tests in that folder. * - * Please be warned that locks currently do nothing on Windows since - * figuring out how to polyfill them correctly is a work in progress. + * On Windows, the Cosmopolitan Libc polyfill for POSIX advisory locks + * only implements enough of its nuances to support SQLite's needs. Some + * possibilities, e.g. punching holes in lock, will raise `ENOTSUP`. * * @param fd is the file descriptor * @param cmd can be one of: @@ -56,12 +58,12 @@ * - `F_SETFL` sets file descriptor status flags * - `F_DUPFD` is like dup() but `arg` is a minimum result, e.g. 3 * - `F_DUPFD_CLOEXEC` ditto but sets `O_CLOEXEC` on returned fd - * - `F_SETLK` for record locking where `arg` is `struct flock` - * - `F_SETLKW` ditto but waits (i.e. blocks) for lock + * - `F_SETLK` for record locking where `arg` is `struct flock *` + * - `F_SETLKW` ditto but waits for lock (SQLite avoids this) * - `F_GETLK` to retrieve information about a record lock - * - `F_OFD_SETLK` for better locks on Linux and XNU - * - `F_OFD_SETLKW` for better locks on Linux and XNU - * - `F_OFD_GETLK` for better locks on Linux and XNU + * - `F_OFD_SETLK` for better non-blocking lock (Linux 3.15+ only) + * - `F_OFD_SETLKW` for better blocking lock (Linux 3.15+ only) + * - `F_OFD_GETLK` for better lock querying (Linux 3.15+ only) * - `F_FULLFSYNC` on MacOS for fsync() with release barrier * - `F_BARRIERFSYNC` on MacOS for fsync() with even more barriers * - `F_SETNOSIGPIPE` on MacOS and NetBSD to control `SIGPIPE` @@ -71,7 +73,7 @@ * - `F_NOCACHE` on MacOS to toggle data caching * - `F_GETPIPE_SZ` on Linux to get pipe size * - `F_SETPIPE_SZ` on Linux to set pipe size - * - `F_NOTIFY` raise `SIGIO` upon `fd` events in `arg` on Linux + * - `F_NOTIFY` raise `SIGIO` upon `fd` events in `arg` (Linux only) * - `DN_ACCESS` for file access * - `DN_MODIFY` for file modifications * - `DN_CREATE` for file creations @@ -88,6 +90,7 @@ * @raise ENOLCK if `F_SETLKW` would have exceeded `RLIMIT_LOCKS` * @raise EPERM if `cmd` is `F_SETOWN` and we weren't authorized * @raise ESRCH if `cmd` is `F_SETOWN` and process group not found + * @raise ENOTSUP on Windows if locking operation isn't supported yet * @raise EDEADLK if `cmd` was `F_SETLKW` and waiting would deadlock * @raise EMFILE if `cmd` is `F_DUPFD` or `F_DUPFD_CLOEXEC` and * `RLIMIT_NOFILE` would be exceeded diff --git a/libc/calls/internal.h b/libc/calls/internal.h index 498b94e5d..68123098d 100644 --- a/libc/calls/internal.h +++ b/libc/calls/internal.h @@ -18,7 +18,6 @@ hidden extern const struct Fd kEmptyFd; int __reservefd(int) hidden; int __reservefd_unlocked(int) hidden; void __releasefd(int) hidden; -void __releasefd_unlocked(int) hidden; int __ensurefds(int) hidden; int __ensurefds_unlocked(int) hidden; void __printfds(void) hidden; @@ -43,7 +42,7 @@ forceinline size_t _clampio(size_t size) { } } -int sys_close_nt(struct Fd *) hidden; +int sys_close_nt(struct Fd *, int) hidden; bool _check_interrupts(bool, struct Fd *) hidden; int sys_openat_metal(int, const char *, int, unsigned); diff --git a/libc/calls/kill.c b/libc/calls/kill.c index fd8f459de..03ecfe4fb 100644 --- a/libc/calls/kill.c +++ b/libc/calls/kill.c @@ -17,10 +17,10 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" -#include "libc/intrin/strace.internal.h" #include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" +#include "libc/intrin/strace.internal.h" #include "libc/str/str.h" /** @@ -38,6 +38,10 @@ * >0 can be SIGINT, SIGTERM, SIGKILL, SIGUSR1, etc. * =0 checks both if pid exists and we can signal it * @return 0 if something was accomplished, or -1 w/ errno + * @raise ESRCH if `pid` couldn't be found + * @raise EPERM if lacked permission to signal process + * @raise EPERM if pledge() is in play without `proc` promised + * @raise EINVAL if the provided `sig` is invalid or unsupported * @asyncsignalsafe */ int kill(int pid, int sig) { diff --git a/libc/calls/open-nt.c b/libc/calls/open-nt.c index d1242f63c..bcc7c032d 100644 --- a/libc/calls/open-nt.c +++ b/libc/calls/open-nt.c @@ -88,7 +88,7 @@ textwindows int sys_open_nt(int dirfd, const char *file, uint32_t flags, rc = sys_open_nt_file(dirfd, file, flags, mode, fd); } if (rc == -1) { - __releasefd_unlocked(fd); + __releasefd(fd); } __fds_unlock(); } diff --git a/libc/calls/pipe-nt.c b/libc/calls/pipe-nt.c index 5bede1c12..1affe6793 100644 --- a/libc/calls/pipe-nt.c +++ b/libc/calls/pipe-nt.c @@ -41,7 +41,7 @@ textwindows int sys_pipe_nt(int pipefd[2], unsigned flags) { return -1; } if ((writer = __reservefd_unlocked(-1)) == -1) { - __releasefd_unlocked(reader); + __releasefd(reader); __fds_unlock(); return -1; } @@ -73,8 +73,8 @@ textwindows int sys_pipe_nt(int pipefd[2], unsigned flags) { CloseHandle(hin); } } - __releasefd_unlocked(writer); - __releasefd_unlocked(reader); + __releasefd(writer); + __releasefd(reader); __fds_unlock(); return -1; } diff --git a/libc/calls/pledge-linux.c b/libc/calls/pledge-linux.c index 0e49c94fa..7e29bd67a 100644 --- a/libc/calls/pledge-linux.c +++ b/libc/calls/pledge-linux.c @@ -1606,19 +1606,26 @@ static privileged void AllowFcntlStdio(struct Filter *f) { // The second argument of fcntl() must be one of: // -// - F_GETLK (5) -// - F_SETLK (6) -// - F_SETLKW (7) +// - F_GETLK (0x05) +// - F_SETLK (0x06) +// - F_SETLKW (0x07) +// - F_OFD_GETLK (0x24) +// - F_OFD_SETLK (0x25) +// - F_OFD_SETLKW (0x26) // static privileged void AllowFcntlLock(struct Filter *f) { static const struct sock_filter fragment[] = { - /*L0*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_fcntl, 0, 6 - 1), - /*L1*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(args[1])), - /*L2*/ BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, 5, 0, 5 - 3), - /*L3*/ BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, 8, 5 - 4, 0), - /*L4*/ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), - /*L5*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(nr)), - /*L6*/ /* next filter */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_fcntl, 0, 9), + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(args[1])), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x05, 5, 0), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x06, 4, 0), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x07, 3, 0), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x24, 2, 0), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x25, 1, 0), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x26, 0, 1), + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(nr)), + /* next filter */ }; AppendFilter(f, PLEDGE(fragment)); } diff --git a/libc/calls/preadv.c b/libc/calls/preadv.c index f005312b9..0657395f1 100644 --- a/libc/calls/preadv.c +++ b/libc/calls/preadv.c @@ -16,12 +16,9 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/assert.h" #include "libc/calls/internal.h" #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.internal.h" -#include "libc/calls/struct/sigset.h" -#include "libc/calls/struct/sigset.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/syscall_support-sysv.internal.h" #include "libc/dce.h" @@ -31,16 +28,13 @@ #include "libc/intrin/likely.h" #include "libc/intrin/strace.internal.h" #include "libc/intrin/weaken.h" -#include "libc/sysv/consts/sig.h" #include "libc/sysv/errfuns.h" #include "libc/zipos/zipos.internal.h" static ssize_t Preadv(int fd, struct iovec *iov, int iovlen, int64_t off) { int e, i; size_t got; - bool masked; ssize_t rc, toto; - sigset_t mask, oldmask; if (fd < 0) { return ebadf(); @@ -94,26 +88,17 @@ static ssize_t Preadv(int fd, struct iovec *iov, int iovlen, int64_t off) { if (rc == -1) { if (!toto) { toto = -1; + } else if (errno != EINTR) { + notpossible; } break; } - got = rc; toto += got; off += got; if (got != iov[i].iov_len) { break; } - - if (!masked) { - sigfillset(&mask); - _npassert(!sys_sigprocmask(SIG_SETMASK, &mask, &oldmask)); - masked = true; - } - } - - if (masked) { - _npassert(!sys_sigprocmask(SIG_SETMASK, &oldmask, 0)); } return toto; diff --git a/libc/calls/pwritev.c b/libc/calls/pwritev.c index f06417b6a..3197ca6c9 100644 --- a/libc/calls/pwritev.c +++ b/libc/calls/pwritev.c @@ -16,12 +16,9 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/assert.h" #include "libc/calls/internal.h" #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.internal.h" -#include "libc/calls/struct/sigset.h" -#include "libc/calls/struct/sigset.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/syscall_support-sysv.internal.h" #include "libc/dce.h" @@ -31,17 +28,14 @@ #include "libc/intrin/likely.h" #include "libc/intrin/strace.internal.h" #include "libc/intrin/weaken.h" -#include "libc/sysv/consts/sig.h" #include "libc/sysv/errfuns.h" #include "libc/zipos/zipos.internal.h" static ssize_t Pwritev(int fd, const struct iovec *iov, int iovlen, int64_t off) { int i, e; - bool masked; size_t sent; ssize_t rc, toto; - sigset_t mask, oldmask; if (fd < 0) { return ebadf(); @@ -95,26 +89,17 @@ static ssize_t Pwritev(int fd, const struct iovec *iov, int iovlen, if (rc == -1) { if (!toto) { toto = -1; + } else if (errno != EINTR) { + notpossible; } break; } - sent = rc; toto += sent; off += sent; if (sent != iov[i].iov_len) { break; } - - if (!masked) { - sigfillset(&mask); - _npassert(!sys_sigprocmask(SIG_SETMASK, &mask, &oldmask)); - masked = true; - } - } - - if (masked) { - _npassert(!sys_sigprocmask(SIG_SETMASK, &oldmask, 0)); } return toto; diff --git a/libc/intrin/releasefd.c b/libc/calls/releasefd.c similarity index 81% rename from libc/intrin/releasefd.c rename to libc/calls/releasefd.c index 505fd9eb5..2efcf13c7 100644 --- a/libc/intrin/releasefd.c +++ b/libc/calls/releasefd.c @@ -17,11 +17,18 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/internal.h" -#include "libc/calls/state.internal.h" +#include "libc/intrin/atomic.h" #include "libc/macros.internal.h" +#include "libc/str/str.h" +// really want to avoid locking here so close() needn't block signals void __releasefd(int fd) { - __fds_lock(); - __releasefd_unlocked(fd); - __fds_unlock(); + int f1, f2; + if (!(0 <= fd && fd < g_fds.n)) return; + bzero(g_fds.p + fd, sizeof(*g_fds.p)); + f1 = atomic_load_explicit(&g_fds.f, memory_order_relaxed); + do { + f2 = MIN(fd, f1); + } while (!atomic_compare_exchange_weak_explicit( + &g_fds.f, &f1, f2, memory_order_release, memory_order_relaxed)); } diff --git a/libc/calls/reservefd.c b/libc/calls/reservefd.c index 28df3135e..4a13960d2 100644 --- a/libc/calls/reservefd.c +++ b/libc/calls/reservefd.c @@ -22,6 +22,7 @@ #include "libc/calls/state.internal.h" #include "libc/calls/struct/sigset.h" #include "libc/dce.h" +#include "libc/intrin/atomic.h" #include "libc/intrin/cmpxchg.h" #include "libc/intrin/extend.internal.h" #include "libc/intrin/strace.internal.h" @@ -36,6 +37,8 @@ #include "libc/sysv/consts/sig.h" #include "libc/sysv/errfuns.h" +// TODO(jart): make more of this code lockless + static volatile size_t mapsize; /** @@ -71,9 +74,10 @@ int __ensurefds(int fd) { * @asyncsignalsafe */ int __reservefd_unlocked(int start) { - int fd; + int fd, f1, f2; for (;;) { - for (fd = MAX(start, g_fds.f); fd < g_fds.n; ++fd) { + f1 = atomic_load_explicit(&g_fds.f, memory_order_acquire); + for (fd = MAX(start, f1); fd < g_fds.n; ++fd) { if (!g_fds.p[fd].kind) { break; } @@ -81,7 +85,11 @@ int __reservefd_unlocked(int start) { fd = __ensurefds_unlocked(fd); bzero(g_fds.p + fd, sizeof(*g_fds.p)); if (_cmpxchg(&g_fds.p[fd].kind, kFdEmpty, kFdReserved)) { - _cmpxchg(&g_fds.f, fd, fd + 1); + // g_fds.f isn't guarded by our mutex + do { + f2 = MAX(fd + 1, f1); + } while (!atomic_compare_exchange_weak_explicit( + &g_fds.f, &f1, f2, memory_order_release, memory_order_relaxed)); return fd; } } diff --git a/libc/calls/sigaction.c b/libc/calls/sigaction.c index 4aab5d495..bea1a434b 100644 --- a/libc/calls/sigaction.c +++ b/libc/calls/sigaction.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/calls/blocksigs.internal.h" #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/calls/sig.internal.h" @@ -29,6 +30,7 @@ #include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/sigaction.internal.h" #include "libc/calls/struct/siginfo.internal.h" +#include "libc/calls/struct/sigset.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/syscall_support-sysv.internal.h" #include "libc/calls/ucontext.h" @@ -474,9 +476,11 @@ int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact) { if (sig == SIGKILL || sig == SIGSTOP) { rc = einval(); } else { + BLOCK_SIGNALS; __sig_lock(); rc = __sigaction(sig, act, oldact); __sig_unlock(); + ALLOW_SIGNALS; } STRACE("sigaction(%G, %s, [%s]) → %d% m", sig, DescribeSigaction(0, act), DescribeSigaction(rc, oldact), rc); diff --git a/libc/intrin/releasefd_unlocked.c b/libc/calls/sigblockall.c similarity index 87% rename from libc/intrin/releasefd_unlocked.c rename to libc/calls/sigblockall.c index 08d9d8a08..9ca00277c 100644 --- a/libc/intrin/releasefd_unlocked.c +++ b/libc/calls/sigblockall.c @@ -16,13 +16,16 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/internal.h" -#include "libc/macros.internal.h" -#include "libc/str/str.h" +#include "libc/calls/struct/sigset.h" -void __releasefd_unlocked(int fd) { - if (0 <= fd && fd < g_fds.n) { - bzero(g_fds.p + fd, sizeof(*g_fds.p)); - g_fds.f = MIN(fd, g_fds.f); - } +/** + * Blocks all signals without strace logging. + * + * @param neu is new signal mask for process + * @return old signal mask + */ +sigset_t _sigblockall(void) { + sigset_t ss; + sigfillset(&ss); + return _sigsetmask(ss); } diff --git a/libc/calls/sigenter-openbsd.c b/libc/calls/sigenter-openbsd.c index 74a9f3e5d..f9b277ee8 100644 --- a/libc/calls/sigenter-openbsd.c +++ b/libc/calls/sigenter-openbsd.c @@ -49,8 +49,8 @@ privileged void __sigenter_openbsd(int sig, struct siginfo_openbsd *openbsdinfo, __repstosb(&g.uc, 0, sizeof(g.uc)); __siginfo2cosmo(&g.si, (void *)openbsdinfo); g.uc.uc_mcontext.fpregs = &g.uc.__fpustate; - __repmovsb(&g.uc.uc_sigmask, &ctx->sc_mask, - MIN(sizeof(g.uc.uc_sigmask), sizeof(ctx->sc_mask))); + g.uc.uc_sigmask.__bits[0] = ctx->sc_mask; + g.uc.uc_sigmask.__bits[1] = 0; g.uc.uc_mcontext.rdi = ctx->sc_rdi; g.uc.uc_mcontext.rsi = ctx->sc_rsi; g.uc.uc_mcontext.rdx = ctx->sc_rdx; diff --git a/libc/calls/sigenter-xnu.c b/libc/calls/sigenter-xnu.c index 14c2d7f2d..89e9381e8 100644 --- a/libc/calls/sigenter-xnu.c +++ b/libc/calls/sigenter-xnu.c @@ -473,6 +473,7 @@ privileged void __sigenter_xnu(void *fn, int infostyle, int sig, if (xnuctx) { g.uc.uc_flags = xnuctx->uc_onstack ? SA_ONSTACK : 0; g.uc.uc_sigmask.__bits[0] = xnuctx->uc_sigmask; + g.uc.uc_sigmask.__bits[1] = 0; g.uc.uc_stack.ss_sp = xnuctx->uc_stack.ss_sp; g.uc.uc_stack.ss_flags = xnuctx->uc_stack.ss_flags; g.uc.uc_stack.ss_size = xnuctx->uc_stack.ss_size; diff --git a/libc/calls/sigprocmask-sysv.greg.c b/libc/calls/sigprocmask-sysv.c similarity index 100% rename from libc/calls/sigprocmask-sysv.greg.c rename to libc/calls/sigprocmask-sysv.c diff --git a/libc/calls/sigprocmask.c b/libc/calls/sigprocmask.c index c59a64ce9..caab7e275 100644 --- a/libc/calls/sigprocmask.c +++ b/libc/calls/sigprocmask.c @@ -26,7 +26,6 @@ #include "libc/intrin/asan.internal.h" #include "libc/intrin/describeflags.internal.h" #include "libc/intrin/strace.internal.h" -#include "libc/log/log.h" #include "libc/str/str.h" #include "libc/sysv/consts/sig.h" #include "libc/sysv/errfuns.h" diff --git a/libc/calls/cfsetospeed.c b/libc/calls/sigsetmask.c similarity index 75% rename from libc/calls/cfsetospeed.c rename to libc/calls/sigsetmask.c index a6ea81adc..5cebef6f5 100644 --- a/libc/calls/cfsetospeed.c +++ b/libc/calls/sigsetmask.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ 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 │ @@ -16,21 +16,25 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/termios.h" -#include "libc/sysv/consts/termios.h" -#include "libc/sysv/errfuns.h" +#include "libc/assert.h" +#include "libc/calls/calls.h" +#include "libc/calls/sig.internal.h" +#include "libc/calls/struct/sigset.internal.h" +#include "libc/dce.h" +#include "libc/sysv/consts/sig.h" -int cfsetospeed(struct termios *t, unsigned speed) { - if (CBAUD) { - if (!(speed & ~CBAUD)) { - t->c_cflag &= ~CBAUD; - t->c_cflag |= speed; - return 0; - } else { - return einval(); - } +/** + * Sets signal mask without strace logging. + * + * @param neu is new signal mask for process + * @return old signal mask + */ +sigset_t _sigsetmask(sigset_t neu) { + sigset_t res; + if (IsMetal() || IsWindows()) { + __sig_mask(SIG_SETMASK, &neu, &res); } else { - t->c_ospeed = speed; - return 0; + _npassert(!sys_sigprocmask(SIG_SETMASK, &neu, &res)); } + return res; } diff --git a/libc/calls/struct/fd.internal.h b/libc/calls/struct/fd.internal.h index fe117b253..a607d7402 100644 --- a/libc/calls/struct/fd.internal.h +++ b/libc/calls/struct/fd.internal.h @@ -25,7 +25,7 @@ struct Fd { }; struct Fds { - int f; /* lowest free slot */ + _Atomic(int) f; /* lowest free slot */ size_t n; struct Fd *p, *e; }; diff --git a/libc/calls/struct/flock.h b/libc/calls/struct/flock.h index eb671cf15..ac897f6db 100644 --- a/libc/calls/struct/flock.h +++ b/libc/calls/struct/flock.h @@ -9,7 +9,7 @@ struct flock { /* cosmopolitan abi */ int64_t l_start; /* starting offset */ int64_t l_len; /* no. bytes (0 means to end of file) */ int32_t l_pid; /* lock owner */ - int32_t l_sysid; /* remote system id or zero for local */ + int32_t l_sysid; /* remote system id or zero for local (freebsd) */ }; COSMOPOLITAN_C_END_ diff --git a/libc/calls/struct/sigset.h b/libc/calls/struct/sigset.h index 97d36bda6..afcace1b2 100644 --- a/libc/calls/struct/sigset.h +++ b/libc/calls/struct/sigset.h @@ -21,6 +21,8 @@ int sigprocmask(int, const sigset_t *, sigset_t *); int sigsuspend(const sigset_t *); int sigpending(sigset_t *); int pthread_sigmask(int, const sigset_t *, sigset_t *); +sigset_t _sigsetmask(sigset_t); +sigset_t _sigblockall(void); COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/calls/syscall-sysv.internal.h b/libc/calls/syscall-sysv.internal.h index 7264cd0f0..cd7f36ee4 100644 --- a/libc/calls/syscall-sysv.internal.h +++ b/libc/calls/syscall-sysv.internal.h @@ -10,8 +10,8 @@ COSMOPOLITAN_C_START_ │ cosmopolitan § syscalls » system five » structless synthetic jump slots ─╬─│┼ ╚────────────────────────────────────────────────────────────────────────────│*/ +axdx_t __sys_fork(void) hidden; axdx_t __sys_pipe(i32[hasatleast 2], i32) hidden; -axdx_t sys_fork(void) hidden; axdx_t sys_getpid(void) hidden; char *sys_getcwd(char *, u64) hidden; char *sys_getcwd_xnu(char *, u64) hidden; @@ -46,6 +46,7 @@ i32 sys_fchownat(i32, const char *, u32, u32, u32) hidden; i32 sys_fcntl(i32, i32, u64) hidden; i32 sys_fdatasync(i32) hidden; i32 sys_flock(i32, i32) hidden; +i32 sys_fork(void) hidden; i32 sys_fsync(i32) hidden; i32 sys_ftruncate(i32, i64, i64) hidden; i32 sys_getcontext(void *) hidden; diff --git a/libc/calls/tcdrain.c b/libc/calls/tcdrain.c index c7ca74c3a..5c6d2a6e3 100644 --- a/libc/calls/tcdrain.c +++ b/libc/calls/tcdrain.c @@ -16,8 +16,41 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/internal.h" +#include "libc/calls/syscall-sysv.internal.h" +#include "libc/calls/syscall_support-nt.internal.h" #include "libc/calls/termios.h" +#include "libc/dce.h" +#include "libc/intrin/strace.internal.h" +#include "libc/nt/files.h" +#include "libc/sysv/errfuns.h" -int tcdrain(int fd) { - return ioctl(fd, TCSBRK, (void *)(intptr_t)1); +static textwindows int sys_tcdrain_nt(int fd) { + if (!__isfdopen(fd)) return ebadf(); + if (!FlushFileBuffers(g_fds.p[fd].handle)) return __winerr(); + return 0; +} + +/** + * Waits until all written output is transmitted. + * + * @param fd is file descriptor of tty + * @raise EBADF if `fd` isn't an open file descriptor + * @raise ENOTTY if `fd` is open but not a teletypewriter + * @raise EIO if process group of writer is orphoned, calling thread is + * not blocking `SIGTTOU`, and process isn't ignoring `SIGTTOU` + * @raise ENOSYS on bare metal + * @asyncsignalsafe + */ +int tcdrain(int fd) { + int rc; + if (IsMetal()) { + rc = enosys(); + } else if (!IsWindows()) { + rc = sys_ioctl(fd, TCSBRK, (void *)(intptr_t)1); + } else { + rc = sys_tcdrain_nt(fd); + } + STRACE("tcdrain(%d) → %d% m", fd, rc); + return rc; } diff --git a/libc/calls/tcflow.c b/libc/calls/tcflow.c index ec1152d90..8015c7abe 100644 --- a/libc/calls/tcflow.c +++ b/libc/calls/tcflow.c @@ -16,31 +16,91 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/internal.h" #include "libc/calls/struct/termios.h" #include "libc/calls/syscall-sysv.internal.h" +#include "libc/calls/syscall_support-nt.internal.h" #include "libc/calls/termios.h" #include "libc/dce.h" +#include "libc/fmt/itoa.h" +#include "libc/intrin/strace.internal.h" +#include "libc/mem/alloca.h" +#include "libc/nt/comms.h" #include "libc/sysv/consts/termios.h" #include "libc/sysv/errfuns.h" +#define kNtPurgeTxabort 1 +#define kNtPurgeRxabort 2 + +static const char *DescribeFlow(char buf[12], int action) { + if (action == TCOOFF) return "TCOOFF"; + if (action == TCOON) return "TCOON"; + if (action == TCIOFF) return "TCIOFF"; + if (action == TCION) return "TCION"; + FormatInt32(buf, action); + return buf; +} + +static int sys_tcflow_bsd(int fd, int action) { + int rc; + uint8_t c; + struct termios t; + if (action == TCOOFF) return sys_ioctl(fd, TIOCSTOP, 0); + if (action == TCOON) return sys_ioctl(fd, TIOCSTART, 0); + if (action != TCIOFF && action != TCION) return einval(); + if (sys_ioctl(fd, TCGETS, &t) == -1) return -1; + c = t.c_cc[action == TCIOFF ? VSTOP : VSTART]; + if (c == 255) return 0; // code is disabled + if (sys_write(fd, &c, 1) == -1) return -1; + return 0; +} + +static dontinline textwindows int sys_tcflow_nt(int fd, int action) { + bool32 ok; + int64_t h; + if (!__isfdopen(fd)) return ebadf(); + h = g_fds.p[fd].handle; + if (action == TCOOFF) { + ok = PurgeComm(h, kNtPurgeTxabort); + } else if (action == TCIOFF) { + ok = PurgeComm(h, kNtPurgeRxabort); + } else if (action == TCOON || action == TCION) { + ok = ClearCommBreak(h); + } else { + return einval(); + } + return ok ? 0 : __winerr(); +} + /** * Changes flow of teletypewriter data. * - * - `TCOOFF` suspends output - * - `TCOON` resumes output - * - `TCIOFF` transmits a STOP character - * - `TCION` transmits a START character + * @param fd is file descriptor of tty + * @param action may be one of: + * - `TCOOFF` to suspend output + * - `TCOON` to resume output + * - `TCIOFF` to transmit a `STOP` character + * - `TCION` to transmit a `START` character + * @return 0 on success, or -1 w/ errno + * @raise EINVAL if `action` is invalid + * @raise ENOSYS on Windows and Bare Metal + * @raise EBADF if `fd` isn't an open file descriptor + * @raise ENOTTY if `fd` is open but not a teletypewriter + * @raise EIO if process group of writer is orphoned, calling thread is + * not blocking `SIGTTOU`, and process isn't ignoring `SIGTTOU` + * @asyncsignalsafe */ int tcflow(int fd, int action) { - uint8_t c; - struct termios t; - if (!IsBsd()) return sys_ioctl(fd, TCXONC, action); - if (action == TCOOFF) return sys_ioctl(fd, TIOCSTOP, 0); - if (action == TCOON) return sys_ioctl(fd, TIOCSTART, 0); - if (action != TCIOFF && action != TCION) return einval(); - if (tcgetattr(fd, &t) == -1) return -1; - if ((c = t.c_cc[action == TCIOFF ? VSTOP : VSTART]) != 255) { - if (sys_write(fd, &c, 1) == -1) return -1; + int rc; + if (IsMetal()) { + rc = enosys(); + } else if (IsBsd()) { + rc = sys_ioctl(fd, TCXONC, action); + } else if (!IsWindows()) { + rc = sys_ioctl(fd, TCXONC, action); + } else { + rc = sys_tcflow_nt(fd, action); } - return 0; + STRACE("tcflow(%d, %s) → %d% m", fd, DescribeFlow(alloca(12), action), rc); + return rc; } diff --git a/libc/calls/tcflush.c b/libc/calls/tcflush.c index 6229b6c68..d23033985 100644 --- a/libc/calls/tcflush.c +++ b/libc/calls/tcflush.c @@ -16,17 +16,70 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/internal.h" +#include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall-sysv.internal.h" +#include "libc/calls/syscall_support-nt.internal.h" #include "libc/calls/termios.h" +#include "libc/dce.h" +#include "libc/fmt/itoa.h" +#include "libc/intrin/strace.internal.h" +#include "libc/mem/alloca.h" +#include "libc/nt/comms.h" +#include "libc/sysv/consts/termios.h" +#include "libc/sysv/errfuns.h" + +#define kNtPurgeTxclear 4 +#define kNtPurgeRxclear 8 + +static const char *DescribeFlush(char buf[12], int action) { + if (action == TCIFLUSH) return "TCIFLUSH"; + if (action == TCOFLUSH) return "TCOFLUSH"; + if (action == TCIOFLUSH) return "TCIOFLUSH"; + FormatInt32(buf, action); + return buf; +} + +static dontinline textwindows int sys_tcflush_nt(int fd, int queue) { + bool32 ok; + int64_t h; + if (!__isfdopen(fd)) return ebadf(); + ok = true; + h = g_fds.p[fd].handle; + if (queue == TCIFLUSH || queue == TCIOFLUSH) { + ok &= !!PurgeComm(h, kNtPurgeRxclear); + } + if (queue == TCOFLUSH || queue == TCIOFLUSH) { + ok &= !!PurgeComm(h, kNtPurgeTxclear); + } + return ok ? 0 : __winerr(); +} /** - * Flushes teletypewriter data. + * Discards queued data on teletypewriter. * - * - `TCIFLUSH` flushes data received but not read - * - `TCOFLUSH` flushes data written but not transmitted - * - `TCIOFLUSH` does both `TCOFLUSH` and `TCIFLUSH` + * @param queue may be one of: + * - `TCIFLUSH` flushes data received but not read + * - `TCOFLUSH` flushes data written but not transmitted + * - `TCIOFLUSH` does both `TCOFLUSH` and `TCIFLUSH` + * @return 0 on success, or -1 w/ errno + * @raise EINVAL if `action` is invalid + * @raise EBADF if `fd` isn't an open file descriptor + * @raise ENOTTY if `fd` is open but not a teletypewriter + * @raise EIO if process group of writer is orphoned, calling thread is + * not blocking `SIGTTOU`, and process isn't ignoring `SIGTTOU` + * @raise ENOSYS on bare metal + * @asyncsignalsafe */ int tcflush(int fd, int queue) { - /* TODO(jart): Windows? */ - return sys_ioctl(fd, TCFLSH, queue); + int rc; + if (IsMetal()) { + rc = enosys(); + } else if (!IsWindows()) { + rc = sys_ioctl(fd, TCFLSH, queue); + } else { + rc = sys_tcflush_nt(fd, queue); + } + STRACE("tcflush(%d, %s) → %d% m", fd, DescribeFlush(alloca(12), queue), rc); + return rc; } diff --git a/libc/calls/tcgetpgrp.c b/libc/calls/tcgetpgrp.c index 292105e28..3ccf7e446 100644 --- a/libc/calls/tcgetpgrp.c +++ b/libc/calls/tcgetpgrp.c @@ -16,16 +16,29 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/calls.h" +#include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/termios.h" +#include "libc/dce.h" +#include "libc/intrin/strace.internal.h" #include "libc/sysv/consts/termios.h" +#include "libc/sysv/errfuns.h" /** * Returns which process group controls terminal. + * + * @return process group id on success, or -1 w/ errno + * @raise ENOTTY if `fd` is isn't controlling teletypewriter + * @raise EBADF if `fd` isn't an open file descriptor + * @raise ENOSYS on Windows and Bare Metal * @asyncsignalsafe */ -int32_t tcgetpgrp(int fd) { - int pgrp; - if (ioctl(fd, TIOCGPGRP, &pgrp) < 0) return -1; - return pgrp; +int tcgetpgrp(int fd) { + int rc, pgrp; + if (IsWindows() || IsMetal()) { + rc = enosys(); + } else { + rc = sys_ioctl(fd, TIOCGPGRP, &pgrp); + } + STRACE("tcgetpgrp(%d) → %d% m", fd, rc == -1 ? rc : pgrp); + return rc == -1 ? rc : pgrp; } diff --git a/libc/calls/tcsendbreak.c b/libc/calls/tcsendbreak.c index 60fbf39d6..f4c4000c0 100644 --- a/libc/calls/tcsendbreak.c +++ b/libc/calls/tcsendbreak.c @@ -17,18 +17,53 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/internal.h" #include "libc/calls/syscall-sysv.internal.h" +#include "libc/calls/syscall_support-nt.internal.h" #include "libc/calls/termios.h" #include "libc/dce.h" +#include "libc/intrin/strace.internal.h" +#include "libc/nt/comms.h" #include "libc/sysv/consts/termios.h" +#include "libc/sysv/errfuns.h" -int tcsendbreak(int fd, int len) { - if (!IsBsd()) { - return sys_ioctl(fd, TCSBRK, 0); - } else { - if (sys_ioctl(fd, TIOCSBRK, 0) == -1) return -1; - usleep(400000); - if (sys_ioctl(fd, TIOCCBRK, 0) == -1) return -1; - return 0; - } +static int sys_tcsendbreak_bsd(int fd) { + if (sys_ioctl(fd, TIOCSBRK, 0) == -1) return -1; + usleep(400000); + if (sys_ioctl(fd, TIOCCBRK, 0) == -1) return -1; + return 0; +} + +static textwindows int sys_tcsendbreak_nt(int fd) { + if (!__isfdopen(fd)) return ebadf(); + if (!TransmitCommChar(g_fds.p[fd].handle, '\0')) return __winerr(); + return 0; +} + +/** + * Sends break. + * + * @param fd is file descriptor of tty + * @param duration of 0 sends a break for 0.25-0.5 seconds, and other + * durations are treated the same by this implementation + * @raise EBADF if `fd` isn't an open file descriptor + * @raise ENOTTY if `fd` is open but not a teletypewriter + * @raise EIO if process group of writer is orphoned, calling thread is + * not blocking `SIGTTOU`, and process isn't ignoring `SIGTTOU` + * @raise ENOSYS on bare metal + * @asyncsignalsafe + */ +int tcsendbreak(int fd, int duration) { + int rc; + if (IsMetal()) { + rc = enosys(); + } else if (IsBsd()) { + rc = sys_tcsendbreak_bsd(fd); + } else if (!IsWindows()) { + rc = sys_ioctl(fd, TCSBRK, 0); + } else { + rc = sys_tcsendbreak_nt(fd); + } + STRACE("tcsendbreak(%d, %u) → %d% m", fd, duration, rc); + return rc; } diff --git a/libc/calls/tcsetpgrp.c b/libc/calls/tcsetpgrp.c index db4bad481..39ee96e13 100644 --- a/libc/calls/tcsetpgrp.c +++ b/libc/calls/tcsetpgrp.c @@ -17,14 +17,33 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/termios.h" +#include "libc/dce.h" +#include "libc/intrin/strace.internal.h" #include "libc/sysv/consts/termios.h" +#include "libc/sysv/errfuns.h" /** - * Puts process group in control of terminal. + * Sets foreground process group id. + * + * @return 0 on success, or -1 w/ errno + * @raise EINVAL if `pgrp` is invalid + * @raise ENOSYS on Windows and Bare Metal + * @raise EBADF if `fd` isn't an open file descriptor + * @raise EPERM if `pgrp` didn't match process in our group + * @raise ENOTTY if `fd` is isn't controlling teletypewriter + * @raise EIO if process group of writer is orphoned, calling thread is + * not blocking `SIGTTOU`, and process isn't ignoring `SIGTTOU` * @asyncsignalsafe */ -int tcsetpgrp(int fd, int32_t pgrp) { - int pgrp_int = pgrp; - return ioctl(fd, TIOCSPGRP, &pgrp_int); +int tcsetpgrp(int fd, int pgrp) { + int rc; + if (IsWindows() || IsMetal()) { + rc = enosys(); + } else { + rc = sys_ioctl(fd, TIOCSPGRP, &pgrp); + } + STRACE("tcsetpgrp(%d, %d) → %d% m", fd, pgrp, rc); + return rc; } diff --git a/libc/calls/cfsetispeed.c b/libc/intrin/_getenv.c similarity index 74% rename from libc/calls/cfsetispeed.c rename to libc/intrin/_getenv.c index 9662e3922..24ce1bc34 100644 --- a/libc/calls/cfsetispeed.c +++ b/libc/intrin/_getenv.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ 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 │ @@ -16,18 +16,26 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/termios.h" -#include "libc/sysv/errfuns.h" +#include "libc/dce.h" +#include "libc/intrin/_getenv.internal.h" -int cfsetispeed(struct termios *t, unsigned speed) { - if (speed) { - if (CBAUD) { - if (speed & ~CBAUD) return einval(); - t->c_cflag &= ~CBAUD; - t->c_cflag |= speed; - } else { - t->c_ispeed = speed; +#define ToUpper(c) \ + (IsWindows() && (c) >= 'a' && (c) <= 'z' ? (c) - 'a' + 'A' : (c)) + +struct Env _getenv(char **p, const char *k) { + char *t; + int i, j; + for (i = 0; (t = p[i]); ++i) { + for (j = 0;; ++j) { + if (!k[j] || k[j] == '=') { + if (!t[j]) return (struct Env){t + j, i}; + if (t[j] == '=') return (struct Env){t + j + 1, i}; + break; + } + if (ToUpper(k[j] & 255) != ToUpper(t[j] & 255)) { + break; + } } } - return 0; + return (struct Env){0, i}; } diff --git a/libc/intrin/_getenv.internal.h b/libc/intrin/_getenv.internal.h new file mode 100644 index 000000000..229665677 --- /dev/null +++ b/libc/intrin/_getenv.internal.h @@ -0,0 +1,15 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN__GETENV_INTERNAL_H_ +#define COSMOPOLITAN_LIBC_INTRIN__GETENV_INTERNAL_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +struct Env { + char *s; + int i; +}; + +struct Env _getenv(char **, const char *) hidden; + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN__GETENV_INTERNAL_H_ */ diff --git a/libc/intrin/bt.c b/libc/intrin/bt.c index 90cb5f239..a1f0c22eb 100644 --- a/libc/intrin/bt.c +++ b/libc/intrin/bt.c @@ -17,10 +17,12 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/errno.h" -#include "libc/intrin/intrin.h" #include "libc/intrin/kprintf.h" #include "libc/intrin/weaken.h" #include "libc/log/backtrace.internal.h" +#include "libc/runtime/symbols.internal.h" + +static _Thread_local bool noreentry; /** * Shows backtrace if crash reporting facilities are linked. @@ -28,12 +30,20 @@ void _bt(const char *fmt, ...) { int e; va_list va; + + if (!noreentry) { + noreentry = true; + } else { + return; + } + if (fmt) { va_start(va, fmt); kvprintf(fmt, va); va_end(va); } - if (_weaken(ShowBacktrace)) { + + if (_weaken(ShowBacktrace) && _weaken(GetSymbolTable)) { e = errno; _weaken(ShowBacktrace)(2, __builtin_frame_address(0)); errno = e; @@ -41,5 +51,18 @@ void _bt(const char *fmt, ...) { kprintf("_bt() can't show backtrace because you need:\n" "\tSTATIC_YOINK(\"ShowBacktrace\");\n" "to be linked.\n"); + if (_weaken(PrintBacktraceUsingSymbols) && _weaken(GetSymbolTable)) { + e = errno; + _weaken(PrintBacktraceUsingSymbols)(2, __builtin_frame_address(0), + _weaken(GetSymbolTable)()); + errno = e; + } else { + kprintf("_bt() can't show backtrace because you need:\n" + "\tSTATIC_YOINK(\"PrintBacktraceUsingSymbols\");\n" + "\tSTATIC_YOINK(\"GetSymbolTable\");\n" + "to be linked.\n"); + } } + + noreentry = false; } diff --git a/libc/runtime/clearenv.c b/libc/intrin/clearenv.c similarity index 96% rename from libc/runtime/clearenv.c rename to libc/intrin/clearenv.c index 59eedfbc5..fe15c9fd4 100644 --- a/libc/runtime/clearenv.c +++ b/libc/intrin/clearenv.c @@ -21,9 +21,11 @@ /** * Removes all environment variables. + * + * @return 0 on success, or nonzero on error */ int clearenv(void) { + environ = 0; STRACE("clearenv() → 0"); - environ = NULL; return 0; } diff --git a/libc/intrin/describeflock.c b/libc/intrin/describeflock.c index 80364479a..950a68b0b 100644 --- a/libc/intrin/describeflock.c +++ b/libc/intrin/describeflock.c @@ -56,7 +56,7 @@ const char *(DescribeFlock)(char buf[N], int cmd, const struct flock *l) { append(", .l_pid=%d", l->l_pid); } - if (l->l_sysid) { + if (IsFreebsd() && l->l_sysid) { append(", .l_sysid=%d", l->l_sysid); } diff --git a/libc/intrin/describesigaction.c b/libc/intrin/describesigaction.c index 06d316e9a..d5ca35f19 100644 --- a/libc/intrin/describesigaction.c +++ b/libc/intrin/describesigaction.c @@ -16,23 +16,68 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" #include "libc/calls/struct/sigaction.h" +#include "libc/calls/struct/sigset.h" #include "libc/calls/struct/sigset.internal.h" #include "libc/dce.h" #include "libc/intrin/asan.internal.h" #include "libc/intrin/describeflags.internal.h" #include "libc/intrin/kprintf.h" +#include "libc/mem/alloca.h" +#include "libc/sysv/consts/sa.h" -const char *(DescribeSigaction)(char buf[256], int rc, +static const char *DescribeSigHandler(char buf[64], void f(int)) { + if (f == SIG_ERR) return "SIG_ERR"; + if (f == SIG_DFL) return "SIG_DFL"; + if (f == SIG_IGN) return "SIG_IGN"; + ksnprintf(buf, 64, "%t", f); + return buf; +} + +static const char *DescribeSigFlags(char buf[64], int x) { + const struct DescribeFlags kSigFlags[] = { + {SA_NOCLDSTOP, "NOCLDSTOP"}, // + {SA_NOCLDWAIT, "NOCLDWAIT"}, // + {SA_SIGINFO, "SIGINFO"}, // + {SA_ONSTACK, "ONSTACK"}, // + {SA_RESTART, "RESTART"}, // + {SA_NODEFER, "NODEFER"}, // + {SA_RESETHAND, "RESETHAND"}, // + {SA_NOMASK, "NOMASK"}, // + {SA_ONESHOT, "ONESHOT"}, // + }; + return DescribeFlags(buf, 64, kSigFlags, ARRAYLEN(kSigFlags), "SA_", x); +} + +#define N 256 + +#define append(...) o += ksnprintf(buf + o, N - o, __VA_ARGS__) + +const char *(DescribeSigaction)(char buf[N], int rc, const struct sigaction *sa) { + int o = 0; + char b64[64]; + if (rc == -1) return "n/a"; if (!sa) return "NULL"; if ((!IsAsan() && kisdangerous(sa)) || (IsAsan() && !__asan_is_valid(sa, sizeof(*sa)))) { - ksnprintf(buf, 256, "%p", sa); - } else { - ksnprintf(buf, 256, "{.sa_handler=%t, .sa_flags=%#lx, .sa_mask=%s}", - sa->sa_handler, sa->sa_flags, DescribeSigset(rc, &sa->sa_mask)); + ksnprintf(buf, N, "%p", sa); + return buf; } + + append("{.sa_handler=%s", DescribeSigHandler(b64, sa->sa_handler)); + + if (sa->sa_flags) { + append(", .sa_flags=%s", DescribeSigFlags(b64, sa->sa_flags)); + } + + if (!sigisemptyset(&sa->sa_mask)) { + append(", .sa_mask=%s", DescribeSigset(rc, &sa->sa_mask)); + } + + append("}"); + return buf; } diff --git a/libc/intrin/describesigset.c b/libc/intrin/describesigset.c index 35a0041fd..87e0ea04f 100644 --- a/libc/intrin/describesigset.c +++ b/libc/intrin/describesigset.c @@ -23,6 +23,7 @@ #include "libc/intrin/kprintf.h" #include "libc/intrin/popcnt.h" #include "libc/str/str.h" +#include "libc/sysv/consts/limits.h" #include "libc/sysv/consts/sig.h" #define N 128 @@ -31,6 +32,7 @@ const char *(DescribeSigset)(char buf[N], int rc, const sigset_t *ss) { bool gotsome; + const char *s; int sig, o = 0; sigset_t sigset; @@ -45,9 +47,9 @@ const char *(DescribeSigset)(char buf[N], int rc, const sigset_t *ss) { if (sigcountset(ss) > 16) { append("~"); sigemptyset(&sigset); - for (sig = 1; sig <= NSIG; ++sig) { + for (sig = 1; sig <= _NSIG; ++sig) { if (!sigismember(ss, sig)) { - sigaddset(&sigset, sig); + sigset.__bits[(sig - 1) >> 6] |= 1ull << ((sig - 1) & 63); } } } else { @@ -56,14 +58,18 @@ const char *(DescribeSigset)(char buf[N], int rc, const sigset_t *ss) { append("{"); gotsome = false; - for (sig = 1; sig <= NSIG; ++sig) { + for (sig = 1; sig <= _NSIG; ++sig) { if (sigismember(&sigset, sig) > 0) { if (gotsome) { append(","); } else { gotsome = true; } - append("%s", strsignal(sig) + 3); + s = strsignal(sig); + if (s[0] == 'S' && s[1] == 'I' && s[2] == 'G') { + s += 3; + } + append("%s", s); } } append("}"); diff --git a/libc/intrin/extend.c b/libc/intrin/extend.c index 392446e1c..50840c28e 100644 --- a/libc/intrin/extend.c +++ b/libc/intrin/extend.c @@ -19,6 +19,7 @@ #include "libc/assert.h" #include "libc/calls/calls.h" #include "libc/dce.h" +#include "libc/errno.h" #include "libc/intrin/asan.internal.h" #include "libc/intrin/asancodes.h" #include "libc/macros.internal.h" @@ -29,17 +30,26 @@ #define G FRAMESIZE -static void _mapframe(void *p, int f) { - int prot, flags; +static void *_mapframe(void *p, int f) { + int rc, prot, flags; struct DirectMap dm; prot = PROT_READ | PROT_WRITE; flags = f | MAP_ANONYMOUS | MAP_FIXED; - _npassert((dm = sys_mmap(p, G, prot, flags, -1, 0)).addr == p); - __mmi_lock(); - _npassert(!TrackMemoryInterval(&_mmi, (uintptr_t)p >> 16, (uintptr_t)p >> 16, - dm.maphandle, prot, flags, false, false, 0, - G)); - __mmi_unlock(); + if ((dm = sys_mmap(p, G, prot, flags, -1, 0)).addr == p) { + __mmi_lock(); + rc = TrackMemoryInterval(&_mmi, (uintptr_t)p >> 16, (uintptr_t)p >> 16, + dm.maphandle, prot, flags, false, false, 0, G); + __mmi_unlock(); + if (!rc) { + return p; + } else { + _unassert(errno == ENOMEM); + return 0; + } + } else { + _unassert(errno == ENOMEM); + return 0; + } } /** @@ -58,7 +68,8 @@ static void _mapframe(void *p, int f) { * @param e points to end of memory that's allocated * @param h is highest address to which `e` may grow * @param f should be `MAP_PRIVATE` or `MAP_SHARED` - * @return new value for `e` + * @return new value for `e` or null w/ errno + * @raise ENOMEM if we require more vespene gas */ noasan void *_extend(void *p, size_t n, void *e, int f, intptr_t h) { char *q; @@ -67,10 +78,10 @@ noasan void *_extend(void *p, size_t n, void *e, int f, intptr_t h) { for (q = e; q < ((char *)p + n); q += 8) { if (!((uintptr_t)q & (G - 1))) { _unassert(q + G <= (char *)h); - _mapframe(q, f); + if (!_mapframe(q, f)) return 0; if (IsAsan()) { if (!((uintptr_t)SHADOW(q) & (G - 1))) { - _mapframe(SHADOW(q), f); + if (!_mapframe(SHADOW(q), f)) return 0; __asan_poison(q, G << kAsanScale, kAsanProtected); } } diff --git a/test/libc/mem/putenv_test.c b/libc/intrin/getenv.c similarity index 72% rename from test/libc/mem/putenv_test.c rename to libc/intrin/getenv.c index d3253ed3e..c5b856e2f 100644 --- a/test/libc/mem/putenv_test.c +++ b/libc/intrin/getenv.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2022 Gavin Arthur Hayes │ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ │ │ │ Permission to use, copy, modify, and/or distribute this software for │ │ any purpose with or without fee is hereby granted, provided that the │ @@ -16,18 +16,28 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/mem/mem.h" +#include "libc/intrin/_getenv.internal.h" +#include "libc/intrin/strace.internal.h" #include "libc/runtime/runtime.h" -#include "libc/testlib/testlib.h" -TEST(putenv, test) { - EXPECT_EQ(0, clearenv()); - EXPECT_EQ(0, putenv("hi=there")); - EXPECT_STREQ("there", getenv("hi")); - EXPECT_EQ(0, clearenv()); - EXPECT_EQ(0, putenv("hi=theretwo")); - EXPECT_STREQ("theretwo", getenv("hi")); - EXPECT_EQ(0, clearenv()); - EXPECT_EQ(0, setenv("hi", "therethree", 0)); - EXPECT_STREQ("therethree", getenv("hi")); +/** + * Returns value of environment variable, or NULL if not found. + * + * Environment variables can store empty string on Unix but not Windows. + * + * @return pointer to value of `environ` entry, or null if not found + */ +char *getenv(const char *s) { + char **p; + struct Env e; + if (!s) return 0; + if (!(p = environ)) return 0; + e = _getenv(p, s); +#if SYSDEBUG + if (!(s[0] == 'T' && s[1] == 'Z' && !s[2])) { + // TODO(jart): memoize TZ or something + STRACE("getenv(%#s) → %#s", s, e.s); + } +#endif + return e.s; } diff --git a/libc/intrin/intrin.mk b/libc/intrin/intrin.mk index ddc5615e2..9cd87f500 100644 --- a/libc/intrin/intrin.mk +++ b/libc/intrin/intrin.mk @@ -7,6 +7,7 @@ LIBC_INTRIN_ARTIFACTS += LIBC_INTRIN_A LIBC_INTRIN = $(LIBC_INTRIN_A_DEPS) $(LIBC_INTRIN_A) LIBC_INTRIN_A = o/$(MODE)/libc/intrin/intrin.a LIBC_INTRIN_A_HDRS = $(filter %.h,$(LIBC_INTRIN_A_FILES)) +LIBC_INTRIN_A_INCS = $(filter %.inc,$(LIBC_INTRIN_A_FILES)) LIBC_INTRIN_A_SRCS_S = $(filter %.S,$(LIBC_INTRIN_A_FILES)) LIBC_INTRIN_A_SRCS_C = $(filter %.c,$(LIBC_INTRIN_A_FILES)) LIBC_INTRIN_A_SRCS = $(LIBC_INTRIN_A_SRCS_S) $(LIBC_INTRIN_A_SRCS_C) @@ -121,7 +122,6 @@ o/$(MODE)/libc/intrin/describeprotflags.o: private \ -fno-sanitize=address o/$(MODE)/libc/intrin/exit1.greg.o \ -o/$(MODE)/libc/intrin/getenv.greg.o \ o/$(MODE)/libc/intrin/wsarecv.o \ o/$(MODE)/libc/intrin/wsarecvfrom.o \ o/$(MODE)/libc/intrin/createfile.o \ @@ -182,6 +182,7 @@ o/$(MODE)/libc/intrin/memmove.o: private \ LIBC_INTRIN_LIBS = $(foreach x,$(LIBC_INTRIN_ARTIFACTS),$($(x))) LIBC_INTRIN_HDRS = $(foreach x,$(LIBC_INTRIN_ARTIFACTS),$($(x)_HDRS)) +LIBC_INTRIN_INCS = $(foreach x,$(LIBC_INTRIN_ARTIFACTS),$($(x)_INCS)) LIBC_INTRIN_SRCS = $(foreach x,$(LIBC_INTRIN_ARTIFACTS),$($(x)_SRCS)) LIBC_INTRIN_CHECKS = $(foreach x,$(LIBC_INTRIN_ARTIFACTS),$($(x)_CHECKS)) LIBC_INTRIN_OBJS = $(foreach x,$(LIBC_INTRIN_ARTIFACTS),$($(x)_OBJS)) diff --git a/libc/intrin/kmalloc.c b/libc/intrin/kmalloc.c index 0e9190f9d..2f2872dea 100644 --- a/libc/intrin/kmalloc.c +++ b/libc/intrin/kmalloc.c @@ -29,7 +29,7 @@ #include "libc/thread/thread.h" #include "libc/thread/tls.h" -#define KMALLOC_ALIGN __BIGGEST_ALIGNMENT__ +#define KMALLOC_ALIGN sizeof(intptr_t) static struct { char *endptr; @@ -55,21 +55,37 @@ __attribute__((__constructor__)) static void kmalloc_init(void) { * The code malloc() depends upon uses this function to allocate memory. * The returned memory can't be freed, and leak detection is impossible. * This function panics when memory isn't available. + * + * Memory returned by this function is aligned on the word size, and as + * such, kmalloc() shouldn't be used for vector operations. + * + * @return zero-initialized memory on success, or null w/ errno + * @raise ENOMEM if we require more vespene gas */ void *kmalloc(size_t size) { - char *start; - size_t i, n; + char *p, *e; + size_t i, n, t; n = ROUNDUP(size + (IsAsan() * 8), KMALLOC_ALIGN); kmalloc_lock(); - i = g_kmalloc.total; - g_kmalloc.total += n; - start = (char *)kMemtrackKmallocStart; - if (!g_kmalloc.endptr) g_kmalloc.endptr = start; - g_kmalloc.endptr = - _extend(start, g_kmalloc.total, g_kmalloc.endptr, MAP_PRIVATE, - kMemtrackKmallocStart + kMemtrackKmallocSize); + t = g_kmalloc.total; + e = g_kmalloc.endptr; + i = t; + t += n; + p = (char *)kMemtrackKmallocStart; + if (!e) e = p; + if ((e = _extend(p, t, e, MAP_PRIVATE, + kMemtrackKmallocStart + kMemtrackKmallocSize))) { + g_kmalloc.endptr = e; + g_kmalloc.total = t; + } else { + p = 0; + } kmalloc_unlock(); - _unassert(!((intptr_t)(start + i) & (KMALLOC_ALIGN - 1))); - if (IsAsan()) __asan_poison(start + i + size, n - size, kAsanHeapOverrun); - return start + i; + if (p) { + _unassert(!((intptr_t)(p + i) & (KMALLOC_ALIGN - 1))); + if (IsAsan()) __asan_poison(p + i + size, n - size, kAsanHeapOverrun); + return p + i; + } else { + return 0; + } } diff --git a/libc/intrin/kmalloc.h b/libc/intrin/kmalloc.h index b26bba057..a2a01a330 100644 --- a/libc/intrin/kmalloc.h +++ b/libc/intrin/kmalloc.h @@ -3,7 +3,7 @@ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ -void *kmalloc(size_t) attributeallocsize((1)) mallocesque returnsnonnull; +void *kmalloc(size_t) mallocesque attributeallocsize((1)) returnsaligned((8)); COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/mem/putenv.c b/libc/intrin/putenv.c similarity index 60% rename from libc/mem/putenv.c rename to libc/intrin/putenv.c index 561adae93..364536347 100644 --- a/libc/mem/putenv.c +++ b/libc/intrin/putenv.c @@ -16,19 +16,16 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/_getenv.internal.h" +#include "libc/intrin/kmalloc.h" #include "libc/intrin/strace.internal.h" -#include "libc/dce.h" #include "libc/macros.internal.h" -#include "libc/mem/alg.h" #include "libc/mem/internal.h" -#include "libc/mem/mem.h" #include "libc/runtime/runtime.h" -#include "libc/str/str.h" -#include "libc/sysv/errfuns.h" #define ToUpper(c) ((c) >= 'a' && (c) <= 'z' ? (c) - 'a' + 'A' : (c)) -static bool once; +static char **expected; static size_t capacity; static size_t GetEnvironLen(char **env) { @@ -37,98 +34,69 @@ static size_t GetEnvironLen(char **env) { return p - env; } -static void RestoreOriginalEnvironment(char **envp) { - environ = envp; -} - -static void PutEnvImplAtExit(void *p) { - free(p); -} - -static void FreeEnviron(char **env) { - char **a; - for (a = env; *a; ++a) { - free(*a); - } - free(env); -} - -static void GrowEnviron(void) { +static char **GrowEnviron(char **a) { size_t n, c; - char **a, **b, **p; - a = environ; + char **b, **p; + if (!a) a = environ; n = a ? GetEnvironLen(a) : 0; c = MAX(16ul, n) << 1; - b = calloc(c, sizeof(char *)); - if (a) { - for (p = b; *a;) { - *p++ = strdup(*a++); + if ((b = kmalloc(c * sizeof(char *)))) { + if (a) { + for (p = b; *a;) { + *p++ = *a++; + } } + environ = b; + expected = b; + capacity = c; + return b; + } else { + return 0; } - __cxa_atexit(FreeEnviron, b, 0); - environ = b; - capacity = c; } int PutEnvImpl(char *s, bool overwrite) { - char *p; - unsigned i, namelen; - if (!once) { - __cxa_atexit(RestoreOriginalEnvironment, environ, 0); - GrowEnviron(); - once = true; - } else if (!environ) { - GrowEnviron(); - } - for (p = s; *p && *p != '='; ++p) { - if (IsWindows()) { - *p = ToUpper(*p); + char **p; + struct Env e; + if (!(p = environ)) { + if (!(p = GrowEnviron(0))) { + return -1; } } - if (*p != '=') goto Fail; - namelen = p + 1 - s; - for (i = 0; environ[i]; ++i) { - if (!strncmp(environ[i], s, namelen)) { - if (!overwrite) { - free(s); - return 0; - } - goto Replace; + e = _getenv(p, s); + if (e.s && !overwrite) { + return 0; + } + if (e.s) { + p[e.i] = s; + return 0; + } + if (p != expected) { + capacity = e.i; + } + if (e.i + 1 >= capacity) { + if (!(p = GrowEnviron(p))) { + return -1; } } - if (i + 1 >= capacity) { - GrowEnviron(); - } - environ[i + 1] = 0; -Replace: - __cxa_atexit(PutEnvImplAtExit, environ[i], 0); - environ[i] = s; + p[e.i + 1] = 0; + p[e.i] = s; return 0; -Fail: - free(s); - return einval(); -} - -static void UnsetenvFree(void *p) { - free(p); -} - -/* weakly called by unsetenv() when removing a pointer */ -void __freeenv(void *p) { - if (once) { - __cxa_atexit(UnsetenvFree, p, 0); - } } /** * Emplaces environment key=value. * - * @return 0 on success or non-zero on error + * @param s should be a string that looks like `"name=value"` and it'll + * become part of the environment; changes to its memory will change + * the environment too + * @return 0 on success, or non-zero w/ errno on error + * @raise ENOMEM if we require more vespene gas * @see setenv(), getenv() */ int putenv(char *s) { int rc; - rc = PutEnvImpl(strdup(s), true); - STRACE("putenv(%#s) → %d", s, rc); + rc = PutEnvImpl(s, true); + STRACE("putenv(%#s) → %d% m", s, rc); return rc; } diff --git a/libc/mem/setenv.c b/libc/intrin/setenv.c similarity index 85% rename from libc/mem/setenv.c rename to libc/intrin/setenv.c index 21af18abb..7879d1bfb 100644 --- a/libc/mem/setenv.c +++ b/libc/intrin/setenv.c @@ -16,10 +16,9 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/safemacros.internal.h" +#include "libc/intrin/kmalloc.h" #include "libc/intrin/strace.internal.h" #include "libc/mem/internal.h" -#include "libc/mem/mem.h" #include "libc/runtime/runtime.h" #include "libc/str/str.h" #include "libc/sysv/errfuns.h" @@ -27,7 +26,7 @@ /** * Copies variable to environment. * - * @return 0 on success, or -1 w/ errno + * @return 0 on success, or -1 w/ errno and environment is unchanged * @raise EINVAL if `name` is empty or contains `'='` * @raise ENOMEM if we require more vespene gas * @see putenv(), getenv() @@ -35,12 +34,16 @@ int setenv(const char *name, const char *value, int overwrite) { int rc; char *s; - size_t namelen, valuelen; - if (isempty(name) || strchr(name, '=')) return einval(); - namelen = strlen(name); - valuelen = strlen(value); - if (!(s = malloc(namelen + valuelen + 2))) return -1; - memcpy(mempcpy(mempcpy(s, name, namelen), "=", 1), value, valuelen + 1); + size_t n, m; + const char *t; + if (!name || !*name || !value) return einval(); + for (t = name; *t; ++t) { + if (*t == '=') return einval(); + } + n = strlen(name); + m = strlen(value); + if (!(s = kmalloc(n + 1 + m + 1))) return -1; + memcpy(mempcpy(mempcpy(s, name, n), "=", 1), value, m + 1); rc = PutEnvImpl(s, overwrite); STRACE("setenv(%#s, %#s, %d) → %d% m", name, value, overwrite, rc); return rc; diff --git a/libc/intrin/sigaddset.c b/libc/intrin/sigaddset.c index a513ad536..b19ac04cf 100644 --- a/libc/intrin/sigaddset.c +++ b/libc/intrin/sigaddset.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/struct/sigset.h" +#include "libc/sysv/consts/limits.h" #include "libc/sysv/consts/sig.h" #include "libc/sysv/errfuns.h" @@ -28,10 +29,16 @@ * @asyncsignalsafe */ int sigaddset(sigset_t *set, int sig) { + _Static_assert(NSIG == sizeof(set->__bits) * CHAR_BIT, ""); _Static_assert(sizeof(set->__bits[0]) * CHAR_BIT == 64, ""); if (1 <= sig && sig <= NSIG) { - if (!sigisprecious(sig)) { - set->__bits[(sig - 1) >> 6] |= 1ull << ((sig - 1) & 63); + if (1 <= sig && sig <= _NSIG) { + if ( +#define M(x) sig != x && +#include "libc/intrin/sigisprecious.inc" + 1) { + set->__bits[(sig - 1) >> 6] |= 1ull << ((sig - 1) & 63); + } } return 0; } else { diff --git a/libc/intrin/sigcountset.c b/libc/intrin/sigcountset.c index 46d3e932c..c1eabf01c 100644 --- a/libc/intrin/sigcountset.c +++ b/libc/intrin/sigcountset.c @@ -19,6 +19,7 @@ #include "libc/calls/struct/sigset.h" #include "libc/intrin/popcnt.h" #include "libc/macros.internal.h" +#include "libc/sysv/consts/limits.h" /** * Returns population count of signal set. @@ -27,9 +28,22 @@ * @asyncsignalsafe */ int sigcountset(const sigset_t *set) { - int r, i; - for (r = i = 0; i < ARRAYLEN(set->__bits); ++i) { - r += popcnt(set->__bits[i]); + int r, i, x, y; + switch (_NSIG) { + case 32: + x = (uint32_t)set->__bits[0]; + y = 0; + break; + case 64: + x = set->__bits[0]; + y = 0; + break; + case 128: + x = set->__bits[0]; + y = set->__bits[1]; + break; + default: + notpossible; } - return r; + return popcnt(x) + popcnt(y); } diff --git a/libc/intrin/sigdelset.c b/libc/intrin/sigdelset.c index 1ab470193..1ddfc5bd6 100644 --- a/libc/intrin/sigdelset.c +++ b/libc/intrin/sigdelset.c @@ -27,6 +27,7 @@ * @asyncsignalsafe */ int sigdelset(sigset_t *set, int sig) { + _Static_assert(NSIG == sizeof(set->__bits) * CHAR_BIT, ""); _Static_assert(sizeof(set->__bits[0]) * CHAR_BIT == 64, ""); if (1 <= sig && sig <= NSIG) { set->__bits[(sig - 1) >> 6] &= ~(1ull << ((sig - 1) & 63)); diff --git a/libc/intrin/sigfillset.c b/libc/intrin/sigfillset.c index 596c6fb19..b2d61f804 100644 --- a/libc/intrin/sigfillset.c +++ b/libc/intrin/sigfillset.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/struct/sigset.h" #include "libc/str/str.h" +#include "libc/sysv/consts/limits.h" #include "libc/sysv/consts/sig.h" /** @@ -29,7 +30,17 @@ */ int sigfillset(sigset_t *set) { memset(set->__bits, -1, sizeof(set->__bits)); - sigdelset(set, SIGKILL); - sigdelset(set, SIGSTOP); +#define M(x) set->__bits[(x - 1) >> 6] &= ~(1ull << ((x - 1) & 63)); +#include "libc/intrin/sigisprecious.inc" + switch (_NSIG) { + case 32: + set->__bits[0] &= 0xffffffff; + break; + case 64: + set->__bits[1] = 0; + break; + default: + break; + } return 0; } diff --git a/libc/intrin/sigismember.c b/libc/intrin/sigismember.c index 279abedbb..ed62cf767 100644 --- a/libc/intrin/sigismember.c +++ b/libc/intrin/sigismember.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/struct/sigset.h" +#include "libc/sysv/consts/limits.h" #include "libc/sysv/errfuns.h" /** @@ -30,7 +31,11 @@ int sigismember(const sigset_t *set, int sig) { _Static_assert(sizeof(set->__bits[0]) * CHAR_BIT == 64, ""); if (1 <= sig && sig <= NSIG) { - return !!(set->__bits[(sig - 1) >> 6] & (1ull << ((sig - 1) & 63))); + if (1 <= sig && sig <= _NSIG) { + return !!(set->__bits[(sig - 1) >> 6] & (1ull << ((sig - 1) & 63))); + } else { + return 0; + } } else { return einval(); } diff --git a/libc/intrin/sigisprecious.c b/libc/intrin/sigisprecious.c index 37bfcb434..1d3bf80c1 100644 --- a/libc/intrin/sigisprecious.c +++ b/libc/intrin/sigisprecious.c @@ -23,6 +23,8 @@ * Returns true if you're not authorized to block this signal. */ int sigisprecious(int sig) { - return sig == SIGKILL || // - sig == SIGSTOP; + return 0 +#define M(x) || sig == x +#include "libc/intrin/sigisprecious.inc" + ; } diff --git a/libc/intrin/sigisprecious.inc b/libc/intrin/sigisprecious.inc new file mode 100644 index 000000000..5c08fd00b --- /dev/null +++ b/libc/intrin/sigisprecious.inc @@ -0,0 +1,3 @@ +M(SIGKILL) +M(SIGABRT) +M(SIGSTOP) diff --git a/libc/intrin/strsignal_r.c b/libc/intrin/strsignal_r.c index 35d27280c..b030f9b80 100644 --- a/libc/intrin/strsignal_r.c +++ b/libc/intrin/strsignal_r.c @@ -33,6 +33,7 @@ * @param buf may be used to store output having at least 15 bytes * @return pointer to .rodata string, or to `buf` after mutating * @see sigaction() + * @asyncsignalsafe * @threadsafe */ char *strsignal_r(int sig, char buf[hasatleast 15]) { diff --git a/libc/calls/unsetenv.c b/libc/intrin/unsetenv.c similarity index 75% rename from libc/calls/unsetenv.c rename to libc/intrin/unsetenv.c index b70bb87a5..440bbc560 100644 --- a/libc/calls/unsetenv.c +++ b/libc/intrin/unsetenv.c @@ -17,39 +17,29 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/dce.h" -#include "libc/intrin/weaken.h" -#include "libc/mem/internal.h" +#include "libc/intrin/_getenv.internal.h" #include "libc/runtime/runtime.h" - -#define ToUpper(c) \ - (IsWindows() && (c) >= 'a' && (c) <= 'z' ? (c) - 'a' + 'A' : (c)) +#include "libc/sysv/errfuns.h" /** * Removes environment variable. + * + * @param s is non-empty environment key which can't contain `'='` + * @return 0 on success, or -1 w/ errno and environment is unchanged + * @raise EINVAL if `s` is an empty string or has a `'='` character */ int unsetenv(const char *s) { - char **p; - size_t i, j, k; - if (s && (p = environ)) { - for (i = 0; p[i]; ++i) { - for (j = 0;; ++j) { - if (!s[j]) { - if (p[i][j] == '=') { - if (_weaken(__freeenv)) { - _weaken(__freeenv)(p[i]); - } - k = i + 1; - do { - p[k - 1] = p[k]; - } while (p[k++]); - return 0; - } - break; - } - if (ToUpper(s[j]) != ToUpper(p[i][j])) { - break; - } - } + char **p, *t; + struct Env e; + if (!s || !*s) return einval(); + for (t = s; *t; ++t) { + if (*t == '=') return einval(); + } + if ((p = environ)) { + e = _getenv(p, s); + while (p[e.i]) { + p[e.i] = p[e.i + 1]; + ++e.i; } } return 0; diff --git a/libc/log/backtrace2.c b/libc/log/backtrace2.c index f64659e73..aee07c0f3 100644 --- a/libc/log/backtrace2.c +++ b/libc/log/backtrace2.c @@ -125,7 +125,7 @@ static int PrintBacktraceUsingAddr2line(int fd, const struct StackFrame *bp) { if (sys_pipe2(pipefds, O_CLOEXEC) == -1) { return -1; } - if ((pid = fork()) == -1) { + if ((pid = __sys_fork().ax) == -1) { sys_close(pipefds[0]); sys_close(pipefds[1]); return -1; @@ -149,19 +149,12 @@ static int PrintBacktraceUsingAddr2line(int fd, const struct StackFrame *bp) { } p1 = buf; p3 = p1 + got; - /* - * remove racist output from gnu tooling, that can't be disabled - * otherwise, since it breaks other tools like emacs that aren't - * equipped to ignore it, and what's most problematic is that - * addr2line somehow manages to put the racism onto the one line - * in the backtrace we actually care about. - */ for (got = p3 - buf, p1 = buf; got;) { if ((p2 = memmem(p1, got, " (discriminator ", strlen(" (discriminator ") - 1)) && (p3 = memchr(p2, '\n', got - (p2 - p1)))) { if (p3 > p2 && p3[-1] == '\r') --p3; - write(2, p1, p2 - p1); + sys_write(2, p1, p2 - p1); got -= p3 - p1; p1 += p3 - p1; } else { @@ -183,11 +176,13 @@ static int PrintBacktraceUsingAddr2line(int fd, const struct StackFrame *bp) { } static int PrintBacktrace(int fd, const struct StackFrame *bp) { +#if !defined(DWARFLESS) if (!IsTiny() && !__isworker) { if (PrintBacktraceUsingAddr2line(fd, bp) != -1) { return 0; } } +#endif return PrintBacktraceUsingSymbols(fd, bp, GetSymbolTable()); } diff --git a/libc/log/backtrace3.c b/libc/log/backtrace3.c index 1ff5522f5..b1193fb2d 100644 --- a/libc/log/backtrace3.c +++ b/libc/log/backtrace3.c @@ -83,7 +83,7 @@ noinstrument noasan int PrintBacktraceUsingSymbols(int fd, } else { addend = 0; } - kprintf("%012lx %012lx %s%+d\n", frame, addr, __get_symbol_name(st, symbol), + kprintf("%012lx %lx %s%+d\n", frame, addr, __get_symbol_name(st, symbol), addend); } return 0; diff --git a/libc/log/log.mk b/libc/log/log.mk index 438a19291..e765c25a9 100644 --- a/libc/log/log.mk +++ b/libc/log/log.mk @@ -42,6 +42,7 @@ LIBC_LOG_A_DIRECTDEPS = \ LIBC_SYSV_CALLS \ LIBC_TIME \ LIBC_TINYMATH \ + LIBC_ZIPOS \ THIRD_PARTY_DLMALLOC \ THIRD_PARTY_GDTOA diff --git a/libc/log/showcrashreports.c b/libc/log/showcrashreports.c index 1723dbb80..24dfa7201 100644 --- a/libc/log/showcrashreports.c +++ b/libc/log/showcrashreports.c @@ -32,6 +32,7 @@ #include "libc/sysv/consts/sig.h" #include "libc/sysv/consts/ss.h" +STATIC_YOINK("zipos"); // for symtab STATIC_YOINK("__die"); // for backtracing STATIC_YOINK("ShowBacktrace"); // for backtracing STATIC_YOINK("GetSymbolTable"); // for backtracing diff --git a/libc/log/vflogf.c b/libc/log/vflogf.c index f2763ae93..83ffa1717 100644 --- a/libc/log/vflogf.c +++ b/libc/log/vflogf.c @@ -18,7 +18,6 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/calls/dprintf.h" -#include "libc/intrin/strace.internal.h" #include "libc/calls/struct/stat.h" #include "libc/calls/struct/timeval.h" #include "libc/dce.h" @@ -27,6 +26,7 @@ #include "libc/fmt/fmt.h" #include "libc/intrin/bits.h" #include "libc/intrin/safemacros.internal.h" +#include "libc/intrin/strace.internal.h" #include "libc/log/internal.h" #include "libc/log/log.h" #include "libc/math.h" @@ -131,7 +131,9 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f, __start_fatal(file, line); strcpy(buf32, "unknown"); gethostname(buf32, sizeof(buf32)); - (dprintf)(STDERR_FILENO, "fatality %s pid %d\n", buf32, getpid()); + (dprintf)(STDERR_FILENO, + "exiting due to aforementioned error (host %s pid %d tid %d)\n", + buf32, getpid(), gettid()); __die(); unreachable; } diff --git a/libc/mem/internal.h b/libc/mem/internal.h index d3517779d..250a60607 100644 --- a/libc/mem/internal.h +++ b/libc/mem/internal.h @@ -10,7 +10,6 @@ struct CritbitNode { }; int PutEnvImpl(char *, bool) hidden; -void __freeenv(void *) hidden; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/mem/sortedints.c b/libc/mem/sortedints.c new file mode 100644 index 000000000..7301d1d15 --- /dev/null +++ b/libc/mem/sortedints.c @@ -0,0 +1,93 @@ +/*-*- 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/assert.h" +#include "libc/dce.h" +#include "libc/intrin/midpoint.h" +#include "libc/mem/mem.h" +#include "libc/mem/sortedints.internal.h" +#include "libc/str/str.h" + +bool ContainsInt(const struct SortedInts *t, int k) { + int l, m, r; + l = 0; + r = t->n - 1; + while (l <= r) { + m = _midpoint(l, r); + if (t->p[m] < k) { + l = m + 1; + } else if (t->p[m] > k) { + r = m - 1; + } else { + return true; + } + } + return false; +} + +int LeftmostInt(const struct SortedInts *t, int k) { + int l, m, r; + l = 0; + r = t->n; + while (l < r) { + m = _midpoint(l, r); + if (t->p[m] < k) { + l = m + 1; + } else { + r = m; + } + } + _unassert(l == 0 || k >= t->p[l - 1]); + _unassert(l == t->n || k <= t->p[l]); + return l; +} + +int CountInt(const struct SortedInts *t, int k) { + int i, c; + for (c = 0, i = LeftmostInt(t, k); i < t->n; ++i) { + if (t->p[i] == k) { + ++c; + } else { + break; + } + } + return c; +} + +bool InsertInt(struct SortedInts *t, int k, bool u) { + int l; + _unassert(t->n >= 0); + _unassert(t->n <= t->c); + if (t->n == t->c) { + ++t->c; + if (!IsModeDbg()) { + t->c += t->c >> 1; + } + t->p = realloc(t->p, t->c * sizeof(*t->p)); + } + l = LeftmostInt(t, k); + if (l < t->n) { + if (u && t->p[l] == k) { + return false; + } + memmove(t->p + l + 1, t->p + l, (t->n - l) * sizeof(*t->p)); + } + t->p[l] = k; + t->n++; + return true; +} diff --git a/libc/mem/sortedints.internal.h b/libc/mem/sortedints.internal.h new file mode 100644 index 000000000..a5ce6f6a3 --- /dev/null +++ b/libc/mem/sortedints.internal.h @@ -0,0 +1,19 @@ +#ifndef COSMOPOLITAN_LIBC_MEM_SORTEDINTS_INTERNAL_H_ +#define COSMOPOLITAN_LIBC_MEM_SORTEDINTS_INTERNAL_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +struct SortedInts { + int n; + int c; + int *p; +}; + +bool ContainsInt(const struct SortedInts *, int); +bool InsertInt(struct SortedInts *, int, bool); +int CountInt(const struct SortedInts *, int); +int LeftmostInt(const struct SortedInts *, int); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_MEM_SORTEDINTS_INTERNAL_H_ */ diff --git a/libc/nexgen32e/longjmp.S b/libc/nexgen32e/longjmp.S index eb96b02fd..4e9de7751 100644 --- a/libc/nexgen32e/longjmp.S +++ b/libc/nexgen32e/longjmp.S @@ -23,9 +23,8 @@ // @param rdi points to the jmp_buf // @param esi is returned by setjmp() invocation (coerced nonzero) // @noreturn -// @assume system five nexgen32e abi conformant -// @note code built w/ microsoft abi compiler can't call this -// @see _gclongjmp() unwinds _gc() destructors +// @see _gclongjmp() +// @see siglongjmp() longjmp: mov %esi,%eax test %eax,%eax @@ -41,4 +40,3 @@ longjmp: jmp *56(%rdi) .endfn longjmp,globl .alias longjmp,_longjmp - .alias longjmp,siglongjmp diff --git a/libc/calls/cfgetospeed.c b/libc/nexgen32e/siglongjmp.S similarity index 77% rename from libc/calls/cfgetospeed.c rename to libc/nexgen32e/siglongjmp.S index f0e57fc16..b1b9e8dff 100644 --- a/libc/calls/cfgetospeed.c +++ b/libc/nexgen32e/siglongjmp.S @@ -1,7 +1,7 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ 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 │ @@ -16,13 +16,14 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/termios.h" -#include "libc/sysv/consts/termios.h" +#include "libc/macros.internal.h" -uint32_t cfgetospeed(const struct termios *t) { - if (CBAUD) { - return t->c_cflag & CBAUD; - } else { - return t->c_ospeed; - } -} +// Loads previously saved processor state. +// +// @param rdi points to the jmp_buf +// @param esi is returned by setjmp() invocation (coerced nonzero) +// @noreturn +// @asyncsignalsafe +siglongjmp: + jmp longjmp + .endfn siglongjmp,globl diff --git a/libc/nt/comms.h b/libc/nt/comms.h new file mode 100644 index 000000000..02b702f08 --- /dev/null +++ b/libc/nt/comms.h @@ -0,0 +1,36 @@ +#ifndef COSMOPOLITAN_LIBC_NT_COMMS_H_ +#define COSMOPOLITAN_LIBC_NT_COMMS_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ +/* ░░░░ + ▒▒▒░░░▒▒▒▒▒▒▒▓▓▓░ + ▒▒▒▒░░░▒▒▒▒▒▒▓▓▓▓▓▓░ + ▒▒▒▒░░░▒▒▒▒▒▒▒▓▓▓▓▓▓ ▒▓░ + ▒▒▒░░░░▒▒▒▒▒▒▓▓▓▓▓▓ ▓▓▓▓▓▓▒ ▒▒▒▓▓█ + ▒▒▒▒░░░▒▒▒▒▒▒▒▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▓▓▓ + ░▒▒▒░░░░▒▒▒▒▒▒▓▓▓▓▓▓ █▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▓▓█ + ▒▒▒▒░░░▒▒▒▒▒▒▒▓▓▓▓▓░ ▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▓▓▓ + ▒▒▒▒░░░▒▒▒▒▒▒▒▓▓▓▓▓▓ ▒▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▓▓▒ + ▒▒▒▒▓▓ ▓▒▒▓▓▓▓ ▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▓▓█ + ▒▓ ▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▓▓ + ░░░░░░░░░░░▒▒▒▒ ▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▓▓█ + ▒▒░░░░░░░░░░▒▒▒▒▒▓▓▓ ▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▓▓▓ + ░▒░░░░░░░░░░░▒▒▒▒▒▓▓ ▓░ ░▓███▓ + ▒▒░░░░░░░░░░▒▒▒▒▒▓▓░ ▒▓▓▓▒▒▒ ░▒▒▒▓ ████████████ + ▒▒░░░░░░░░░░░▒▒▒▒▒▓▓ ▒▓▓▓▓▒▒▒▒▒▒▒▒░░░▒▒▒▒▒░ ░███ + ▒░░░░░░░░░░░▒▒▒▒▒▓▓ ▓▓▓▓▒▒▒▒▒▒▒▒░░░░▒▒▒▒▓ ███ + ▒▒░░░░░░░░░░▒▒▒▒▒▒▓▓ ▒▓▓▓▒▒▒▒▒▒▒▒░░░░▒▒▒▒▒ ▓██ + ▒░░░░░░░░░░░▒▒▒▒▒▓▓ ▓▓▓▓▒▒▒▒▒▒▒▒░░░▒▒▒▒▒▓ ▓██ + ▒▒░░░▒▒▒░░░▒▒░▒▒▒▓▓▒ ▒▓▓▓▒▒▒▒▒▒▒▒░░░░▒▒▒▒▒ ███ + ░▒▓ ░▓▓▓▓▒▒▒▒▒▒▒▒░░░░▒▒▒▒▓ ▓██ +╔────────────────────────────────────────────────────────────────▀▀▀─────────│─╗ +│ cosmopolitan § new technology » communications ─╬─│┼ +╚────────────────────────────────────────────────────────────────────────────│*/ + +bool32 PurgeComm(int64_t hFile, uint32_t dwFlags); +bool32 TransmitCommChar(int64_t hFile, char cChar); +bool32 ClearCommBreak(int64_t hFile); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_NT_COMMS_H_ */ diff --git a/libc/nt/kernel32/ClearCommBreak.s b/libc/nt/kernel32/ClearCommBreak.s index e9dbac01f..af9012f5e 100644 --- a/libc/nt/kernel32/ClearCommBreak.s +++ b/libc/nt/kernel32/ClearCommBreak.s @@ -1,2 +1,15 @@ .include "o/libc/nt/codegen.inc" .imp kernel32,__imp_ClearCommBreak,ClearCommBreak,0 + + .text.windows +ClearCommBreak: + push %rbp + mov %rsp,%rbp + .profilable + mov %rdi,%rcx + sub $32,%rsp + call *__imp_ClearCommBreak(%rip) + leave + ret + .endfn ClearCommBreak,globl + .previous diff --git a/libc/nt/kernel32/PurgeComm.s b/libc/nt/kernel32/PurgeComm.s index fa7496653..d1d3a441d 100644 --- a/libc/nt/kernel32/PurgeComm.s +++ b/libc/nt/kernel32/PurgeComm.s @@ -1,2 +1,12 @@ .include "o/libc/nt/codegen.inc" .imp kernel32,__imp_PurgeComm,PurgeComm,0 + + .text.windows +PurgeComm: + push %rbp + mov %rsp,%rbp + .profilable + mov __imp_PurgeComm(%rip),%rax + jmp __sysv2nt + .endfn PurgeComm,globl + .previous diff --git a/libc/nt/kernel32/TransmitCommChar.s b/libc/nt/kernel32/TransmitCommChar.s index 4c594a188..45756320d 100644 --- a/libc/nt/kernel32/TransmitCommChar.s +++ b/libc/nt/kernel32/TransmitCommChar.s @@ -1,2 +1,12 @@ .include "o/libc/nt/codegen.inc" .imp kernel32,__imp_TransmitCommChar,TransmitCommChar,0 + + .text.windows +TransmitCommChar: + push %rbp + mov %rsp,%rbp + .profilable + mov __imp_TransmitCommChar(%rip),%rax + jmp __sysv2nt + .endfn TransmitCommChar,globl + .previous diff --git a/libc/nt/master.sh b/libc/nt/master.sh index ac463d4b5..439ef2ae1 100755 --- a/libc/nt/master.sh +++ b/libc/nt/master.sh @@ -112,7 +112,7 @@ imp 'CheckNameLegalDOS8Dot3' CheckNameLegalDOS8Dot3W kernel32 128 imp 'CheckRemoteDebuggerPresent' CheckRemoteDebuggerPresent kernel32 0 2 imp 'CheckTokenCapability' CheckTokenCapability kernel32 0 imp 'CheckTokenMembershipEx' CheckTokenMembershipEx kernel32 0 -imp 'ClearCommBreak' ClearCommBreak kernel32 0 +imp 'ClearCommBreak' ClearCommBreak kernel32 0 1 imp 'ClearCommError' ClearCommError kernel32 0 imp 'CloseConsoleHandle' CloseConsoleHandle kernel32 134 imp 'ClosePackageInfo' ClosePackageInfo kernel32 0 @@ -764,7 +764,7 @@ imp 'PssWalkMarkerSetPosition' PssWalkMarkerSetPosition kernel32 0 imp 'PssWalkMarkerTell' PssWalkMarkerTell kernel32 1080 imp 'PssWalkSnapshot' PssWalkSnapshot kernel32 0 imp 'PulseEvent' PulseEvent kernel32 0 1 -imp 'PurgeComm' PurgeComm kernel32 0 +imp 'PurgeComm' PurgeComm kernel32 0 2 imp 'QueryActCtx' QueryActCtxW kernel32 0 imp 'QueryActCtxSettings' QueryActCtxSettingsW kernel32 0 imp 'QueryActCtxSettingsWWorker' QueryActCtxSettingsWWorker kernel32 1085 @@ -1011,7 +1011,7 @@ imp 'TlsGetValue' TlsGetValue kernel32 0 1 imp 'TlsSetValue' TlsSetValue kernel32 0 2 imp 'Toolhelp32ReadProcessMemory' Toolhelp32ReadProcessMemory kernel32 1449 imp 'TransactNamedPipe' TransactNamedPipe kernel32 0 7 -imp 'TransmitCommChar' TransmitCommChar kernel32 0 +imp 'TransmitCommChar' TransmitCommChar kernel32 0 2 imp 'TryAcquireSRWLockExclusive' TryAcquireSRWLockExclusive kernel32 0 1 imp 'TryAcquireSRWLockShared' TryAcquireSRWLockShared kernel32 0 1 imp 'TryEnterCriticalSection' TryEnterCriticalSection kernel32 0 1 diff --git a/libc/runtime/cocmd.c b/libc/runtime/cocmd.c index 6c63a53af..3100fff88 100644 --- a/libc/runtime/cocmd.c +++ b/libc/runtime/cocmd.c @@ -25,6 +25,7 @@ #include "libc/fmt/conv.h" #include "libc/fmt/itoa.h" #include "libc/fmt/magnumstrs.internal.h" +#include "libc/intrin/_getenv.internal.h" #include "libc/intrin/bits.h" #include "libc/intrin/weaken.h" #include "libc/macros.internal.h" @@ -55,11 +56,6 @@ #define TOMBSTONE ((char *)-1) #define READ24(s) READ32LE(s "\0") -struct Env { - char *s; - int i; -}; - static char *p; static char *q; static char *r; @@ -71,7 +67,7 @@ static char *assign; static char var[32]; static int lastchild; static int exitstatus; -static char *envs[3000]; +static char *envs[500]; static char *args[3000]; static const char *prog; static char errbuf[512]; @@ -148,27 +144,9 @@ static int GetSignalByName(const char *s) { return 0; } -static struct Env GetEnv(char **p, const char *k) { - int i, j; - for (i = 0; p[i]; ++i) { - for (j = 0;; ++j) { - if (!k[j] || k[j] == '=') { - if (p[i][j] == '=') { - return (struct Env){p[i] + j + 1, i}; - } - break; - } - if (toupper(k[j] & 255) != toupper(p[i][j] & 255)) { - break; - } - } - } - return (struct Env){0, i}; -} - static void PutEnv(char **p, const char *kv) { struct Env e; - e = GetEnv(p, kv); + e = _getenv(p, kv); p[e.i] = kv; if (!e.s) p[e.i + 1] = 0; } @@ -271,7 +249,7 @@ static int Read(void) { static int Cd(void) { const char *s; - if ((s = n > 1 ? args[1] : GetEnv(envs, "HOME").s)) { + if ((s = n > 1 ? args[1] : _getenv(envs, "HOME").s)) { if (!chdir(s)) { return 0; } else { @@ -489,14 +467,27 @@ static const char *IntToStr(int x) { } static const char *GetVar(const char *key) { + static char vbuf[PATH_MAX]; if (key[0] == '$' && !key[1]) { return IntToStr(getpid()); } else if (key[0] == '!' && !key[1]) { return IntToStr(lastchild); } else if (key[0] == '?' && !key[1]) { return IntToStr(exitstatus); + } else if (!strcmp(key, "PWD")) { + _npassert(getcwd(vbuf, sizeof(vbuf))); + return vbuf; + } else if (!strcmp(key, "UID")) { + FormatInt32(vbuf, getuid()); + return vbuf; + } else if (!strcmp(key, "GID")) { + FormatInt32(vbuf, getgid()); + return vbuf; + } else if (!strcmp(key, "EUID")) { + FormatInt32(vbuf, geteuid()); + return vbuf; } else { - return GetEnv(envs, key).s; + return _getenv(envs, key).s; } } diff --git a/libc/runtime/enable_threads.c b/libc/runtime/enable_threads.c index 21f0176d6..72211596b 100644 --- a/libc/runtime/enable_threads.c +++ b/libc/runtime/enable_threads.c @@ -19,8 +19,10 @@ #include "ape/sections.internal.h" #include "libc/assert.h" #include "libc/calls/calls.h" +#include "libc/calls/struct/sigset.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/intrin/strace.internal.h" +#include "libc/runtime/morph.h" #include "libc/runtime/runtime.h" #include "libc/thread/tls.h" @@ -30,7 +32,8 @@ extern int __threadcalls_start[]; #pragma weak __threadcalls_end static privileged dontinline void FixupLockNops(void) { - __morph_begin(); + sigset_t mask; + __morph_begin(&mask); /* * _NOPL("__threadcalls", func) * @@ -54,7 +57,7 @@ static privileged dontinline void FixupLockNops(void) { _base[*p + 1] = 0x67; _base[*p + 2] = 0xe8; } - __morph_end(); + __morph_end(&mask); } void __enable_threads(void) { diff --git a/libc/runtime/enable_tls.c b/libc/runtime/enable_tls.c index 2a5117234..419b6bd8c 100644 --- a/libc/runtime/enable_tls.c +++ b/libc/runtime/enable_tls.c @@ -19,6 +19,7 @@ #include "ape/sections.internal.h" #include "libc/assert.h" #include "libc/calls/calls.h" +#include "libc/calls/struct/sigset.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" #include "libc/errno.h" @@ -32,6 +33,7 @@ #include "libc/nexgen32e/msr.h" #include "libc/nt/thread.h" #include "libc/runtime/internal.h" +#include "libc/runtime/morph.h" #include "libc/runtime/runtime.h" #include "libc/stdalign.internal.h" #include "libc/str/str.h" @@ -196,9 +198,10 @@ privileged void __enable_tls(void) { if ((intptr_t)_tls_content && (IsWindows() || IsXnu())) { int n; uint64_t w; + sigset_t mask; unsigned m, dis; unsigned char *p; - __morph_begin(); + __morph_begin(&mask); if (IsXnu()) { // Apple is quite straightforward to patch. We basically @@ -262,7 +265,7 @@ privileged void __enable_tls(void) { } } - __morph_end(); + __morph_end(&mask); } // we are now allowed to use tls diff --git a/libc/runtime/fork-nt.c b/libc/runtime/fork-nt.c index 0f59649ab..ffc24b366 100644 --- a/libc/runtime/fork-nt.c +++ b/libc/runtime/fork-nt.c @@ -224,9 +224,6 @@ textwindows void WinMainForked(void) { AbortFork("CloseHandle"); } - // turn tls back on - __enable_tls(); - // rewrap the stdin named pipe hack // since the handles closed on fork struct Fds *fds = VEIL("r", &g_fds); @@ -267,11 +264,14 @@ textwindows int sys_fork_nt(uint32_t dwCreationFlags) { uint32_t oldprot; char **args, **args2; char16_t pipename[64]; + bool needtls, threaded; int64_t reader, writer; struct NtStartupInfo startinfo; int i, n, pid, untrackpid, rc = -1; char *p, forkvar[6 + 21 + 1 + 21 + 1]; struct NtProcessInformation procinfo; + threaded = __threaded; + needtls = __tls_enabled; if (!setjmp(jb)) { pid = untrackpid = __reservefd_unlocked(-1); reader = CreateNamedPipe(CreatePipeName(pipename), @@ -345,9 +345,15 @@ textwindows int sys_fork_nt(uint32_t dwCreationFlags) { } } else { rc = 0; + if (needtls) { + __enable_tls(); + } + if (threaded && !__threaded && _weaken(__enable_threads)) { + _weaken(__enable_threads)(); + } } if (untrackpid != -1) { - __releasefd_unlocked(untrackpid); + __releasefd(untrackpid); } return rc; } diff --git a/libc/calls/cfgetispeed.c b/libc/runtime/fork-sysv.c similarity index 82% rename from libc/calls/cfgetispeed.c rename to libc/runtime/fork-sysv.c index 51fefc120..9a7aba589 100644 --- a/libc/calls/cfgetispeed.c +++ b/libc/runtime/fork-sysv.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ 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 │ @@ -16,13 +16,19 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/termios.h" -#include "libc/sysv/consts/termios.h" +#include "libc/calls/syscall-sysv.internal.h" +#include "libc/dce.h" -uint32_t cfgetispeed(const struct termios *t) { - if (CBAUD) { - return t->c_cflag & CBAUD; - } else { - return t->c_ispeed; +int sys_fork(void) { + axdx_t ad; + int ax, dx; + ad = __sys_fork(); + ax = ad.ax; + dx = ad.dx; + if (IsXnu() && ax != -1) { + // eax always returned with childs pid + // edx is 0 for parent and 1 for child + ax &= dx - 1; } + return ax; } diff --git a/libc/runtime/fork.c b/libc/runtime/fork.c index 4c5fec362..48da63f7a 100644 --- a/libc/runtime/fork.c +++ b/libc/runtime/fork.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/calls/blocksigs.internal.h" #include "libc/calls/calls.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/struct/sigset.internal.h" @@ -33,30 +34,15 @@ #include "libc/thread/tls.h" int _fork(uint32_t dwCreationFlags) { - axdx_t ad; - bool threaded; - sigset_t old, all; int ax, dx, parent; - sigfillset(&all); - _unassert(!sigprocmask(SIG_BLOCK, &all, &old)); + BLOCK_SIGNALS; if (__threaded && _weaken(_pthread_onfork_prepare)) { _weaken(_pthread_onfork_prepare)(); } if (!IsWindows()) { - ad = sys_fork(); - ax = ad.ax; - dx = ad.dx; - if (IsXnu() && ax != -1) { - // eax always returned with childs pid - // edx is 0 for parent and 1 for child - ax &= dx - 1; - } + ax = sys_fork(); } else { - threaded = __threaded; ax = sys_fork_nt(dwCreationFlags); - if (threaded && !__threaded && _weaken(__enable_threads)) { - _weaken(__enable_threads)(); - } } if (!ax) { if (!IsWindows()) { @@ -81,7 +67,7 @@ int _fork(uint32_t dwCreationFlags) { } STRACE("fork() → %d% m", ax); } - _unassert(!sigprocmask(SIG_SETMASK, &old, 0)); + ALLOW_SIGNALS; return ax; } @@ -92,6 +78,7 @@ int _fork(uint32_t dwCreationFlags) { * @raise EAGAIN if `RLIMIT_NPROC` was exceeded or system lacked resources * @raise ENOMEM if we require more vespene gas * @asyncsignalsafe + * @threadsafe */ int fork(void) { return _fork(0); diff --git a/libc/runtime/hook.greg.c b/libc/runtime/hook.greg.c index 7f8ac9456..04145bfbb 100644 --- a/libc/runtime/hook.greg.c +++ b/libc/runtime/hook.greg.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "ape/sections.internal.h" #include "libc/calls/struct/sigset.h" +#include "libc/runtime/morph.h" #include "libc/runtime/runtime.h" #include "libc/runtime/symbols.internal.h" @@ -45,13 +46,13 @@ privileged noinstrument noasan int __hook(void *ifunc, size_t i; char *p, *pe; intptr_t addr; + sigset_t mask; uint64_t code, mcode; - sigset_t mask, oldmask; intptr_t kMcount = (intptr_t)&mcount; intptr_t kProgramCodeStart = (intptr_t)_ereal; intptr_t kPrivilegedStart = (intptr_t)__privileged_addr; if (!symbols) return -1; - __morph_begin(); + __morph_begin(&mask); for (i = 0; i < symbols->count; ++i) { if (symbols->addr_base + symbols->symbols[i].x < kProgramCodeStart) { continue; @@ -112,6 +113,6 @@ privileged noinstrument noasan int __hook(void *ifunc, } } } - __morph_end(); + __morph_end(&mask); return 0; } diff --git a/libc/runtime/memtrack.internal.h b/libc/runtime/memtrack.internal.h index 80454f27f..1235e36a3 100644 --- a/libc/runtime/memtrack.internal.h +++ b/libc/runtime/memtrack.internal.h @@ -135,39 +135,39 @@ forceinline pureconst bool IsFixedFrame(int x) { } forceinline pureconst bool OverlapsImageSpace(const void *p, size_t n) { - const unsigned char *start, *ender; + const unsigned char *BegA, *EndA, *BegB, *EndB; if (n) { - start = p; - ender = start + (n - 1); - return ((_base <= start && start < _end) || - (_base <= ender && ender < _end) || - (start < _base && _end <= ender)); + BegA = p; + EndA = BegA + (n - 1); + BegB = _base; + EndB = _end - 1; + return MAX(BegA, BegB) < MIN(EndA, EndB); } else { return 0; } } forceinline pureconst bool OverlapsArenaSpace(const void *p, size_t n) { - intptr_t x, y; + intptr_t BegA, EndA, BegB, EndB; if (n) { - x = (intptr_t)p; - y = x + (n - 1); - return ((0x50000000 <= x && x <= 0x7ffdffff) || - (0x50000000 <= y && y <= 0x7ffdffff) || - (x < 0x50000000 && 0x7ffdffff < y)); + BegA = (intptr_t)p; + EndA = BegA + (n - 1); + BegB = 0x50000000; + EndB = 0x7ffdffff; + return MAX(BegA, BegB) < MIN(EndA, EndB); } else { return 0; } } forceinline pureconst bool OverlapsShadowSpace(const void *p, size_t n) { - intptr_t x, y; + intptr_t BegA, EndA, BegB, EndB; if (n) { - x = (intptr_t)p; - y = x + (n - 1); - return ((0x7fff0000 <= x && x <= 0x10007fffffff) || - (0x7fff0000 <= y && y <= 0x10007fffffff) || - (x < 0x7fff0000 && 0x10007fffffff < y)); + BegA = (intptr_t)p; + EndA = BegA + (n - 1); + BegB = 0x7fff0000; + EndB = 0x10007fffffff; + return MAX(BegA, BegB) < MIN(EndA, EndB); } else { return 0; } diff --git a/libc/runtime/morph.greg.c b/libc/runtime/morph.greg.c index 27fcf7930..5d86e4e55 100644 --- a/libc/runtime/morph.greg.c +++ b/libc/runtime/morph.greg.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #define ShouldUseMsabiAttribute() 1 #include "ape/sections.internal.h" +#include "libc/assert.h" #include "libc/calls/internal.h" #include "libc/calls/struct/sigset.h" #include "libc/dce.h" @@ -37,9 +38,6 @@ __msabi extern typeof(VirtualProtect) *const __imp_VirtualProtect; -static int64_t vector; -static sigset_t oldss; - static privileged void __morph_mprotect(void *addr, size_t size, int prot, int ntprot) { bool cf; @@ -58,7 +56,7 @@ static privileged void __morph_mprotect(void *addr, size_t size, int prot, _Exit(26); } #endif - if (ax) notpossible; + _npassert(!ax); } else { __imp_VirtualProtect(addr, size, ntprot, &op); } @@ -69,29 +67,26 @@ static privileged void __morph_mprotect(void *addr, size_t size, int prot, * * @return 0 on success, or -1 w/ errno */ -privileged void __morph_begin(void) { +privileged void __morph_begin(sigset_t *save) { int ax; bool cf; intptr_t dx; sigset_t ss = {{-1, -1}}; STRACE("__morph_begin()"); - if (!IsWindows()) { - if (!IsOpenbsd()) { - asm volatile("mov\t$8,%%r10d\n\t" - "syscall" - : "=a"(ax), "=d"(dx) - : "0"(__NR_sigprocmask), "D"(SIG_BLOCK), "S"(&ss), - "1"(&oldss) - : "rcx", "r8", "r9", "r10", "r11", "memory", "cc"); - if (ax) notpossible; - } else { - asm volatile(CFLAG_ASM("syscall") - : CFLAG_CONSTRAINT(cf), "=a"(ax), "=d"(dx) - : "1"(__NR_sigprocmask), "D"(SIG_BLOCK), "S"(-1u) - : "rcx", "r8", "r9", "r10", "r11", "memory"); - oldss.__bits[0] = ax & 0xffffffff; - if (cf) notpossible; - } + if (IsOpenbsd()) { + asm volatile(CFLAG_ASM("syscall") + : CFLAG_CONSTRAINT(cf), "=a"(ax), "=d"(dx) + : "1"(__NR_sigprocmask), "D"(SIG_BLOCK), "S"(-1u) + : "rcx", "r8", "r9", "r10", "r11", "memory"); + save->__bits[0] = ax & 0xffffffff; + _npassert(!cf); + } else if (!IsWindows() && !IsMetal()) { + asm volatile("mov\t$8,%%r10d\n\t" + "syscall" + : "=a"(ax), "=d"(dx) + : "0"(__NR_sigprocmask), "D"(SIG_BLOCK), "S"(&ss), "1"(save) + : "rcx", "r8", "r9", "r10", "r11", "memory", "cc"); + _npassert(!ax); } __morph_mprotect(_base, __privileged_addr - _base, PROT_READ | PROT_WRITE, kNtPageWritecopy); @@ -100,29 +95,25 @@ privileged void __morph_begin(void) { /** * Begins code morphing executable. */ -privileged void __morph_end(void) { +privileged void __morph_end(sigset_t *save) { int ax; long dx; bool cf; __morph_mprotect(_base, __privileged_addr - _base, PROT_READ | PROT_EXEC, kNtPageExecuteRead); - if (!IsWindows()) { - if (!IsOpenbsd()) { - asm volatile("mov\t$8,%%r10d\n\t" - "syscall" - : "=a"(ax), "=d"(dx) - : "0"(__NR_sigprocmask), "D"(SIG_SETMASK), "S"(&oldss), - "1"(0) - : "rcx", "r8", "r9", "r10", "r11", "memory", "cc"); - if (ax) notpossible; - } else { - asm volatile(CFLAG_ASM("syscall") - : CFLAG_CONSTRAINT(cf), "=a"(ax), "=d"(dx) - : "1"(__NR_sigprocmask), "D"(SIG_SETMASK), - "S"(oldss.__bits[0]) - : "rcx", "r8", "r9", "r10", "r11", "memory"); - if (cf) notpossible; - } + if (IsOpenbsd()) { + asm volatile(CFLAG_ASM("syscall") + : CFLAG_CONSTRAINT(cf), "=a"(ax), "=d"(dx) + : "1"(__NR_sigprocmask), "D"(SIG_SETMASK), "S"(save->__bits[0]) + : "rcx", "r8", "r9", "r10", "r11", "memory"); + _npassert(!cf); + } else if (!IsWindows() && !IsMetal()) { + asm volatile("mov\t$8,%%r10d\n\t" + "syscall" + : "=a"(ax), "=d"(dx) + : "0"(__NR_sigprocmask), "D"(SIG_SETMASK), "S"(save), "1"(0) + : "rcx", "r8", "r9", "r10", "r11", "memory", "cc"); + _npassert(!ax); } STRACE("__morph_end()"); } diff --git a/libc/runtime/morph.h b/libc/runtime/morph.h new file mode 100644 index 000000000..813140317 --- /dev/null +++ b/libc/runtime/morph.h @@ -0,0 +1,12 @@ +#ifndef COSMOPOLITAN_LIBC_RUNTIME_MORPH_H_ +#define COSMOPOLITAN_LIBC_RUNTIME_MORPH_H_ +#include "libc/calls/struct/sigset.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void __morph_begin(sigset_t *); +void __morph_end(sigset_t *); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_RUNTIME_MORPH_H_ */ diff --git a/libc/runtime/runtime.h b/libc/runtime/runtime.h index 954556c1b..b6e59d0a5 100644 --- a/libc/runtime/runtime.h +++ b/libc/runtime/runtime.h @@ -57,7 +57,7 @@ int atfork(void *, void *) libcesque; int atexit(void (*)(void)) libcesque; char *getenv(const char *) nosideeffect libcesque; int putenv(char *) paramsnonnull(); -int setenv(const char *, const char *, int) paramsnonnull(); +int setenv(const char *, const char *, int); int unsetenv(const char *); int clearenv(void); void fpreset(void); @@ -100,8 +100,6 @@ char *GetInterpreterExecutableName(char *, size_t); void __printargs(const char *); void __paginate(int, const char *); int __arg_max(void); -void __morph_begin(void); -void __morph_end(void); void __print_maps(void); void __warn_if_powersave(void); const char *__describe_os(void); diff --git a/libc/runtime/straceinit.greg.c b/libc/runtime/straceinit.greg.c index e47bede51..8eeff8ef3 100644 --- a/libc/runtime/straceinit.greg.c +++ b/libc/runtime/straceinit.greg.c @@ -16,6 +16,8 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/dce.h" +#include "libc/intrin/atomic.h" #include "libc/intrin/safemacros.internal.h" #include "libc/intrin/strace.internal.h" #include "libc/log/libfatal.internal.h" @@ -29,7 +31,7 @@ textstartup int __strace_init(int argc, char **argv, char **envp, long *auxv) { /* asan isn't initialized yet at runlevel 300 */ if (__intercept_flag(&argc, argv, "--strace") || __atoul(nulltoempty(__getenv(envp, "STRACE")))) { - ++__strace; + atomic_store_explicit(&__strace, 1, memory_order_relaxed); } return (__argc = argc); } diff --git a/libc/runtime/vfork.S b/libc/runtime/vfork.S index 7bb00df25..c6febc9a1 100644 --- a/libc/runtime/vfork.S +++ b/libc/runtime/vfork.S @@ -20,7 +20,6 @@ #include "libc/intrin/strace.internal.h" #include "libc/thread/tls.h" #include "libc/macros.internal.h" -.privileged // Forks process without copying page tables. // @@ -32,37 +31,55 @@ // do anything in a vfork()'d child process. TLS memory must not // be disabled (it's enabled by default) since vfork() needs it. // +// What makes vfork() dangerous is that any changes to memory in +// the child process can happen in the parent too. The exception +// to this rule is `errno` which is saved/restored in a register +// by this implementation. However, despite its dangers, vfork's +// performance is irresistible and wonderous to behold. If safer +// code is desired, consider posix_spawn() which uses vfork(). +// // Do not make the assumption that the parent is suspended until -// the child terminates since this impl calls fork() on Windows, -// OpenBSD, and MacOS. +// the child terminates since this uses the raw fork system call +// on Windows, OpenBSD, and MacOS. In that case the process will +// proceed without blocking the parent; however, the `__vforked` +// variable is still set to true in the child, so lock functions +// won't do anything, and other functions shall change behavior. +// This ensures that, even if the operating system does not give +// us the performance of vfork(), we'll still be able to cut out +// the libc overhead, e.g. pthread_atfork(). // // @return pid of child process or 0 if forked process // @returnstwice // @threadsafe // @vforksafe -vfork: call __require_tls - xor %edi,%edi # dwCreationFlags -#ifdef __SANITIZE_ADDRESS__ - jmp fork # TODO: asan and vfork don't mix? - .endfn vfork,globl -#else -#if SupportsWindows() - testb IsWindows() - jnz sys_fork_nt # and we're lucky to have that -#endif -#if SupportsXnu() - testb IsXnu() - jnz fork -#endif -#if SupportsOpenbsd() - testb IsOpenbsd() - jnz fork # fake vfork plus msyscall issues -#endif +vfork: +#if !IsTiny() + push %rbp + mov %rsp,%rbp + .profilable + call __require_tls #ifdef SYSDEBUG ezlea .Llog,di call __stracef -#endif /* SYSDEBUG */ +#endif + pop %rbp +#endif mov %fs:0,%r9 # get thread information block +#if SupportsWindows() + testb IsWindows() + jnz 6f # and we're lucky to have that +#endif +#ifdef __SANITIZE_ADDRESS__ + jmp 5f # TODO: asan and vfork don't mix? +#endif +#if SupportsXnu() + testb IsXnu() + jnz 5f +#endif +#if SupportsOpenbsd() + testb IsOpenbsd() + jnz 5f # fake vfork plus msyscall issues +#endif mov 0x3c(%r9),%r8d # avoid question of @vforksafe errno pop %rsi # saves return address in a register mov __NR_vfork(%rip),%eax @@ -79,12 +96,35 @@ vfork: call __require_tls cmp $-4095,%eax jae systemfive_error mov %r8d,0x3c(%r9) # restore errno - test %eax,%eax - jnz .Lprnt -.Lchld: orb $TIB_FLAG_VFORKED,0x40(%r9) +1: test %eax,%eax + jnz .Lpar +.Lchi: orb $TIB_FLAG_VFORKED,0x40(%r9) ret -.Lprnt: andb $~TIB_FLAG_VFORKED,0x40(%r9) +.Lpar: andb $~TIB_FLAG_VFORKED,0x40(%r9) ret +#if SupportsXnu() || SupportsOpenbsd() || defined(__SANITIZE_ADDRESS__) +5: push %rbp + mov %rsp,%rbp + push %r9 + push %r9 + call sys_fork + pop %r9 + pop %r9 + pop %rbp + jmp 1b +#endif +#if SupportsWindows() +6: push %rbp + mov %rsp,%rbp + push %r9 + push %r9 + xor %edi,%edi # dwCreationFlags + call sys_fork_nt + pop %r9 + pop %r9 + pop %rbp + jmp 1b +#endif .endfn vfork,globl #ifdef SYSDEBUG @@ -93,5 +133,3 @@ vfork: call __require_tls .asciz "vfork()\n" .previous #endif /* DEBUGSYS */ - -#endif /* __SANITIZE_ADDRESS__ */ diff --git a/libc/sock/socketpair-nt.c b/libc/sock/socketpair-nt.c index e4ad00122..8ec12a741 100644 --- a/libc/sock/socketpair-nt.c +++ b/libc/sock/socketpair-nt.c @@ -95,8 +95,8 @@ textwindows int sys_socketpair_nt(int family, int type, int proto, int sv[2]) { rc = 0; } else { CloseHandle(hpipe); - __releasefd_unlocked(writer); - __releasefd_unlocked(reader); + __releasefd(writer); + __releasefd(reader); rc = -1; } diff --git a/libc/stdio/getrandom.c b/libc/stdio/getrandom.c index 7f1a8e6a6..9df21814a 100644 --- a/libc/stdio/getrandom.c +++ b/libc/stdio/getrandom.c @@ -129,8 +129,9 @@ ssize_t getrandom(void *p, size_t n, unsigned f) { static textstartup void getrandom_init(void) { int e, rc; - e = errno; struct sigaction sa, oldsa; + if (IsWindows()) return; + e = errno; if (IsBsd()) { sa.sa_flags = 0; sa.sa_handler = SIG_IGN; diff --git a/libc/str/str.h b/libc/str/str.h index 0ee028e81..ef1e03b14 100644 --- a/libc/str/str.h +++ b/libc/str/str.h @@ -3,6 +3,9 @@ #define INVALID_CODEPOINT 0xfffd +#define _tolower(u) (0040 | (u)) +#define _toupper(u) (0137 & (u)) + #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ diff --git a/libc/sysv/calls/__sys_fork.s b/libc/sysv/calls/__sys_fork.s new file mode 100644 index 000000000..3a39a32f5 --- /dev/null +++ b/libc/sysv/calls/__sys_fork.s @@ -0,0 +1,2 @@ +.include "o/libc/sysv/macros.internal.inc" +.scall __sys_fork,0x0020020022002039,globl,hidden diff --git a/libc/sysv/calls/sys_fork.s b/libc/sysv/calls/sys_fork.s deleted file mode 100644 index 17643ee98..000000000 --- a/libc/sysv/calls/sys_fork.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/macros.internal.inc" -.scall sys_fork,0x0020020022002039,globl,hidden diff --git a/libc/sysv/consts.sh b/libc/sysv/consts.sh index 68ce27681..813784629 100755 --- a/libc/sysv/consts.sh +++ b/libc/sysv/consts.sh @@ -385,9 +385,9 @@ syscon fcntl FWRITE 0 2 2 2 2 0 # wut is it syscon fcntl F_SETLK 6 8 12 8 8 6 # polyfilled nt syscon fcntl F_SETLKW 7 9 13 9 9 7 # polyfilled nt syscon fcntl F_GETLK 5 7 11 7 7 5 # polyfilled nt -syscon fcntl F_OFD_SETLK 37 90 -1 -1 -1 6 -syscon fcntl F_OFD_SETLKW 38 91 -1 -1 -1 7 -syscon fcntl F_OFD_GETLK 36 92 -1 -1 -1 5 +syscon fcntl F_OFD_SETLK 37 -1 -1 -1 -1 -1 # listed in xnu source code but marked private +syscon fcntl F_OFD_SETLKW 38 -1 -1 -1 -1 -1 # listed in xnu source code but marked private +syscon fcntl F_OFD_GETLK 36 -1 -1 -1 -1 -1 # listed in xnu source code but marked private syscon fcntl F_RDLCK 0 1 1 1 1 0 # polyfilled nt; bsd consensus syscon fcntl F_WRLCK 1 3 3 3 3 1 # polyfilled nt; bsd consensus syscon fcntl F_UNLCK 2 2 2 2 2 2 # polyfilled nt; unix consensus @@ -1340,8 +1340,8 @@ syscon compat TIOCSETAF 0x5404 0x80487416 0x802c7416 0x802c7416 0x802c74 syscon termios TIOCGWINSZ 0x5413 1074295912 1074295912 1074295912 1074295912 0x5413 # ioctl(tty, TIOCGWINSZ, struct winsize *argp); polyfilled NT syscon termios TIOCSWINSZ 0x5414 0x80087467 0x80087467 0x80087467 0x80087467 0x5414 # ioctl(tty, TIOCSWINSZ, const struct winsize *argp) (faked NT) syscon termios TIOCOUTQ 0x5411 0x40047473 0x40047473 0x40047473 0x40047473 0 # get # bytes queued in TTY's output buffer ioctl(tty, TIOCSWINSZ, const struct winsize *argp) -syscon termios TIOCGPGRP 0x540f 0x40047477 0x40047477 0x40047477 0x40047477 0 # get pgrp of tty -syscon termios TIOCSPGRP 0x5410 0x80047476 0x80047476 0x80047476 0x80047476 0 # set pgrp of tty +syscon termios TIOCGPGRP 0x540f 0x40047477 0x40047477 0x40047477 0x40047477 0 # tcgetpgrp(): get pgrp of tty +syscon termios TIOCSPGRP 0x5410 0x80047476 0x80047476 0x80047476 0x80047476 0 # tcsetpgrp(): set pgrp of tty syscon termios TIOCSBRK 0x5427 0x2000747b 0x2000747b 0x2000747b 0x2000747b 0 # set break bit syscon termios TIOCCBRK 0x5428 0x2000747a 0x2000747a 0x2000747a 0x2000747a 0 # boop syscon termios TIOCCONS 0x541d 0x80047462 0x80047462 0x80047462 0x80047462 0 # boop @@ -1356,8 +1356,7 @@ syscon termios TIOCGSID 0x5429 0x40047463 0x40047463 0x40047463 0x400474 syscon termios TABLDISC 0 0x3 0 0x3 0x3 0 # boop syscon termios SLIPDISC 0 0x4 0x4 0x4 0x4 0 # boop syscon termios PPPDISC 0 0x5 0x5 0x5 0x5 0 # boop -syscon termios TCSBRK 0x5409 0x2000745e 0x2000745e 0x2000745e 0x2000745e 0 # TIOCDRAIN on BSD -syscon termios TIOCDRAIN 0x5409 0x2000745e 0x2000745e 0x2000745e 0x2000745e 0 # TCSBRK on Linux +syscon termios TCSBRK 0x5409 0x2000745e 0x2000745e 0x2000745e 0x2000745e 0 # TIOCDRAIN on BSD; TIOCDRAIN on BSD syscon termios TIOCSTAT 0 0x20007465 0x20007465 0x20007465 0x20007465 0 # boop syscon termios TIOCSTART 0 0x2000746e 0x2000746e 0x2000746e 0x2000746e 0 # boop syscon termios TIOCCDTR 0 0x20007478 0x20007478 0x20007478 0x20007478 0 # clear data terminal ready @@ -2308,35 +2307,38 @@ syscon misc AIO_ALLDONE 2 1 3 0 0 0 syscon misc AIO_NOTCANCELED 1 4 2 0 0 0 syscon misc AIO_CANCELED 0 2 1 0 0 0 -syscon baud B0 0 0 0 0 0 0 # consensus -syscon baud B50 1 50 50 50 50 0 # bsd consensus -syscon baud B75 2 75 75 75 75 0 # bsd consensus -syscon baud B110 3 110 110 110 110 0 # bsd consensus -syscon baud B134 4 134 134 134 134 0 # bsd consensus -syscon baud B150 5 150 150 150 150 0 # bsd consensus -syscon baud B200 6 200 200 200 200 0 # bsd consensus -syscon baud B300 7 300 300 300 300 0 # bsd consensus -syscon baud B600 8 600 600 600 600 0 # bsd consensus -syscon baud B1200 9 0x04b0 0x04b0 0x04b0 0x04b0 0 # bsd consensus -syscon baud B1800 10 0x0708 0x0708 0x0708 0x0708 0 # bsd consensus -syscon baud B2400 11 0x0960 0x0960 0x0960 0x0960 0 # bsd consensus -syscon baud B4800 12 0x12c0 0x12c0 0x12c0 0x12c0 0 # bsd consensus -syscon baud B9600 13 0x2580 0x2580 0x2580 0x2580 0 # bsd consensus -syscon baud B19200 14 0x4b00 0x4b00 0x4b00 0x4b00 0 # bsd consensus -syscon baud B38400 15 0x9600 0x9600 0x9600 0x9600 0 # bsd consensus -syscon baud B57600 0x1001 0xe100 0xe100 0xe100 0xe100 0 # bsd consensus -syscon baud B115200 0x1002 0x01c200 0x01c200 0x01c200 0x01c200 0 # bsd consensus -syscon baud B230400 0x1003 0x038400 0x038400 0x038400 0x038400 0 # bsd consensus -syscon baud B500000 0x1005 0 0 0 0 0 -syscon baud B576000 0x1006 0 0 0 0 0 -syscon baud B1000000 0x1008 0 0 0 0 0 -syscon baud B1152000 0x1009 0 0 0 0 0 -syscon baud B1500000 0x100a 0 0 0 0 0 -syscon baud B2000000 0x100b 0 0 0 0 0 -syscon baud B2500000 0x100c 0 0 0 0 0 -syscon baud B3000000 0x100d 0 0 0 0 0 -syscon baud B3500000 0x100e 0 0 0 0 0 -syscon baud B4000000 0x100f 0 0 0 0 0 +# baud rates +# +# group name GNU/Systemd XNU's Not UNIX! FreeBSD OpenBSD NetBSD The New Technology Commentary +syscon baud B0 0 0 0 0 0 0 +syscon baud B50 1 50 50 50 50 50 +syscon baud B75 2 75 75 75 75 75 +syscon baud B110 3 110 110 110 110 110 +syscon baud B134 4 134 134 134 134 134 +syscon baud B150 5 150 150 150 150 150 +syscon baud B200 6 200 200 200 200 200 +syscon baud B300 7 300 300 300 300 300 +syscon baud B600 8 600 600 600 600 600 +syscon baud B1200 9 1200 1200 1200 1200 1200 +syscon baud B1800 10 1800 1800 1800 1800 1800 +syscon baud B2400 11 2400 2400 2400 2400 2400 +syscon baud B4800 12 4800 4800 4800 4800 4800 +syscon baud B9600 13 9600 9600 9600 9600 9600 +syscon baud B19200 14 19200 19200 19200 19200 19200 +syscon baud B38400 15 38400 38400 38400 38400 38400 +syscon baud B57600 0x1001 57600 57600 57600 57600 57600 +syscon baud B115200 0x1002 115200 115200 115200 115200 115200 +syscon baud B230400 0x1003 230400 230400 230400 230400 230400 +syscon baud B500000 0x1005 500000 500000 500000 500000 500000 +syscon baud B576000 0x1006 576000 576000 576000 576000 576000 +syscon baud B1000000 0x1008 1000000 1000000 1000000 1000000 1000000 +syscon baud B1152000 0x1009 1152000 1152000 1152000 1152000 1152000 +syscon baud B1500000 0x100a 1500000 1500000 1500000 1500000 1500000 +syscon baud B2000000 0x100b 2000000 2000000 2000000 2000000 2000000 +syscon baud B2500000 0x100c 2500000 2500000 2500000 2500000 2500000 +syscon baud B3000000 0x100d 3000000 3000000 3000000 3000000 3000000 +syscon baud B3500000 0x100e 3500000 3500000 3500000 3500000 3500000 +syscon baud B4000000 0x100f 4000000 4000000 4000000 4000000 4000000 syscon misc WEOF 0xffffffff -1 -1 -1 -1 -1 # bsd consensus (win fake) syscon misc _LINUX_QUOTA_VERSION 2 0 0 0 0 0 diff --git a/libc/sysv/consts/B1000000.s b/libc/sysv/consts/B1000000.s index 6474b8cf8..c6c8ae9ac 100644 --- a/libc/sysv/consts/B1000000.s +++ b/libc/sysv/consts/B1000000.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B1000000,0x1008,0,0,0,0,0 +.syscon baud,B1000000,0x1008,1000000,1000000,1000000,1000000,1000000 diff --git a/libc/sysv/consts/B110.s b/libc/sysv/consts/B110.s index 9f5767b2c..a49b1ede3 100644 --- a/libc/sysv/consts/B110.s +++ b/libc/sysv/consts/B110.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B110,3,110,110,110,110,0 +.syscon baud,B110,3,110,110,110,110,110 diff --git a/libc/sysv/consts/B115200.s b/libc/sysv/consts/B115200.s index 3eec71805..1cd70186d 100644 --- a/libc/sysv/consts/B115200.s +++ b/libc/sysv/consts/B115200.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B115200,0x1002,0x01c200,0x01c200,0x01c200,0x01c200,0 +.syscon baud,B115200,0x1002,115200,115200,115200,115200,115200 diff --git a/libc/sysv/consts/B1152000.s b/libc/sysv/consts/B1152000.s index e55931ba0..21b17045d 100644 --- a/libc/sysv/consts/B1152000.s +++ b/libc/sysv/consts/B1152000.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B1152000,0x1009,0,0,0,0,0 +.syscon baud,B1152000,0x1009,1152000,1152000,1152000,1152000,1152000 diff --git a/libc/sysv/consts/B1200.s b/libc/sysv/consts/B1200.s index e8cc5bc4a..c4ae5e9db 100644 --- a/libc/sysv/consts/B1200.s +++ b/libc/sysv/consts/B1200.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B1200,9,0x04b0,0x04b0,0x04b0,0x04b0,0 +.syscon baud,B1200,9,1200,1200,1200,1200,1200 diff --git a/libc/sysv/consts/B134.s b/libc/sysv/consts/B134.s index dd93e5564..3150b1fc3 100644 --- a/libc/sysv/consts/B134.s +++ b/libc/sysv/consts/B134.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B134,4,134,134,134,134,0 +.syscon baud,B134,4,134,134,134,134,134 diff --git a/libc/sysv/consts/B150.s b/libc/sysv/consts/B150.s index 88b9db955..5468176ab 100644 --- a/libc/sysv/consts/B150.s +++ b/libc/sysv/consts/B150.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B150,5,150,150,150,150,0 +.syscon baud,B150,5,150,150,150,150,150 diff --git a/libc/sysv/consts/B1500000.s b/libc/sysv/consts/B1500000.s index 462d2321f..9bb71dc25 100644 --- a/libc/sysv/consts/B1500000.s +++ b/libc/sysv/consts/B1500000.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B1500000,0x100a,0,0,0,0,0 +.syscon baud,B1500000,0x100a,1500000,1500000,1500000,1500000,1500000 diff --git a/libc/sysv/consts/B1800.s b/libc/sysv/consts/B1800.s index 297dfdb3f..6b17873f6 100644 --- a/libc/sysv/consts/B1800.s +++ b/libc/sysv/consts/B1800.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B1800,10,0x0708,0x0708,0x0708,0x0708,0 +.syscon baud,B1800,10,1800,1800,1800,1800,1800 diff --git a/libc/sysv/consts/B19200.s b/libc/sysv/consts/B19200.s index 3768ccb53..2be3aa9ea 100644 --- a/libc/sysv/consts/B19200.s +++ b/libc/sysv/consts/B19200.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B19200,14,0x4b00,0x4b00,0x4b00,0x4b00,0 +.syscon baud,B19200,14,19200,19200,19200,19200,19200 diff --git a/libc/sysv/consts/B200.s b/libc/sysv/consts/B200.s index 6254fac46..942b23f81 100644 --- a/libc/sysv/consts/B200.s +++ b/libc/sysv/consts/B200.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B200,6,200,200,200,200,0 +.syscon baud,B200,6,200,200,200,200,200 diff --git a/libc/sysv/consts/B2000000.s b/libc/sysv/consts/B2000000.s index 67a3bd0a8..9a2266176 100644 --- a/libc/sysv/consts/B2000000.s +++ b/libc/sysv/consts/B2000000.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B2000000,0x100b,0,0,0,0,0 +.syscon baud,B2000000,0x100b,2000000,2000000,2000000,2000000,2000000 diff --git a/libc/sysv/consts/B230400.s b/libc/sysv/consts/B230400.s index 5a78ac7dd..e1d0a7756 100644 --- a/libc/sysv/consts/B230400.s +++ b/libc/sysv/consts/B230400.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B230400,0x1003,0x038400,0x038400,0x038400,0x038400,0 +.syscon baud,B230400,0x1003,230400,230400,230400,230400,230400 diff --git a/libc/sysv/consts/B2400.s b/libc/sysv/consts/B2400.s index a426d7adf..b5e704ddc 100644 --- a/libc/sysv/consts/B2400.s +++ b/libc/sysv/consts/B2400.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B2400,11,0x0960,0x0960,0x0960,0x0960,0 +.syscon baud,B2400,11,2400,2400,2400,2400,2400 diff --git a/libc/sysv/consts/B2500000.s b/libc/sysv/consts/B2500000.s index 95adf3939..1acb797b4 100644 --- a/libc/sysv/consts/B2500000.s +++ b/libc/sysv/consts/B2500000.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B2500000,0x100c,0,0,0,0,0 +.syscon baud,B2500000,0x100c,2500000,2500000,2500000,2500000,2500000 diff --git a/libc/sysv/consts/B300.s b/libc/sysv/consts/B300.s index 99e550c2b..296d10fe4 100644 --- a/libc/sysv/consts/B300.s +++ b/libc/sysv/consts/B300.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B300,7,300,300,300,300,0 +.syscon baud,B300,7,300,300,300,300,300 diff --git a/libc/sysv/consts/B3000000.s b/libc/sysv/consts/B3000000.s index 47cb70c5f..6d4f71572 100644 --- a/libc/sysv/consts/B3000000.s +++ b/libc/sysv/consts/B3000000.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B3000000,0x100d,0,0,0,0,0 +.syscon baud,B3000000,0x100d,3000000,3000000,3000000,3000000,3000000 diff --git a/libc/sysv/consts/B3500000.s b/libc/sysv/consts/B3500000.s index 4a8dc2cb4..a86415186 100644 --- a/libc/sysv/consts/B3500000.s +++ b/libc/sysv/consts/B3500000.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B3500000,0x100e,0,0,0,0,0 +.syscon baud,B3500000,0x100e,3500000,3500000,3500000,3500000,3500000 diff --git a/libc/sysv/consts/B38400.s b/libc/sysv/consts/B38400.s index 739187e9e..0d10b17ea 100644 --- a/libc/sysv/consts/B38400.s +++ b/libc/sysv/consts/B38400.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B38400,15,0x9600,0x9600,0x9600,0x9600,0 +.syscon baud,B38400,15,38400,38400,38400,38400,38400 diff --git a/libc/sysv/consts/B4000000.s b/libc/sysv/consts/B4000000.s index 009a89244..f83baaba0 100644 --- a/libc/sysv/consts/B4000000.s +++ b/libc/sysv/consts/B4000000.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B4000000,0x100f,0,0,0,0,0 +.syscon baud,B4000000,0x100f,4000000,4000000,4000000,4000000,4000000 diff --git a/libc/sysv/consts/B4800.s b/libc/sysv/consts/B4800.s index 495c51443..99134b6b1 100644 --- a/libc/sysv/consts/B4800.s +++ b/libc/sysv/consts/B4800.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B4800,12,0x12c0,0x12c0,0x12c0,0x12c0,0 +.syscon baud,B4800,12,4800,4800,4800,4800,4800 diff --git a/libc/sysv/consts/B50.s b/libc/sysv/consts/B50.s index d0d3f6cd2..c010ae819 100644 --- a/libc/sysv/consts/B50.s +++ b/libc/sysv/consts/B50.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B50,1,50,50,50,50,0 +.syscon baud,B50,1,50,50,50,50,50 diff --git a/libc/sysv/consts/B500000.s b/libc/sysv/consts/B500000.s index 21a399ebe..d5c0dba10 100644 --- a/libc/sysv/consts/B500000.s +++ b/libc/sysv/consts/B500000.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B500000,0x1005,0,0,0,0,0 +.syscon baud,B500000,0x1005,500000,500000,500000,500000,500000 diff --git a/libc/sysv/consts/B57600.s b/libc/sysv/consts/B57600.s index e72b66f6b..77306126a 100644 --- a/libc/sysv/consts/B57600.s +++ b/libc/sysv/consts/B57600.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B57600,0x1001,0xe100,0xe100,0xe100,0xe100,0 +.syscon baud,B57600,0x1001,57600,57600,57600,57600,57600 diff --git a/libc/sysv/consts/B576000.s b/libc/sysv/consts/B576000.s index 125379cf8..5b46b3c65 100644 --- a/libc/sysv/consts/B576000.s +++ b/libc/sysv/consts/B576000.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B576000,0x1006,0,0,0,0,0 +.syscon baud,B576000,0x1006,576000,576000,576000,576000,576000 diff --git a/libc/sysv/consts/B600.s b/libc/sysv/consts/B600.s index e7ba226d7..9443aeb53 100644 --- a/libc/sysv/consts/B600.s +++ b/libc/sysv/consts/B600.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B600,8,600,600,600,600,0 +.syscon baud,B600,8,600,600,600,600,600 diff --git a/libc/sysv/consts/B75.s b/libc/sysv/consts/B75.s index c5c2a1f0e..61f34b2cf 100644 --- a/libc/sysv/consts/B75.s +++ b/libc/sysv/consts/B75.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B75,2,75,75,75,75,0 +.syscon baud,B75,2,75,75,75,75,75 diff --git a/libc/sysv/consts/B9600.s b/libc/sysv/consts/B9600.s index c981da0bc..adacf6629 100644 --- a/libc/sysv/consts/B9600.s +++ b/libc/sysv/consts/B9600.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon baud,B9600,13,0x2580,0x2580,0x2580,0x2580,0 +.syscon baud,B9600,13,9600,9600,9600,9600,9600 diff --git a/libc/sysv/consts/F_OFD_GETLK.s b/libc/sysv/consts/F_OFD_GETLK.s index 61ac76389..592a7b9e1 100644 --- a/libc/sysv/consts/F_OFD_GETLK.s +++ b/libc/sysv/consts/F_OFD_GETLK.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon fcntl,F_OFD_GETLK,36,92,-1,-1,-1,5 +.syscon fcntl,F_OFD_GETLK,36,-1,-1,-1,-1,-1 diff --git a/libc/sysv/consts/F_OFD_SETLK.s b/libc/sysv/consts/F_OFD_SETLK.s index 8330b673d..d9b807b95 100644 --- a/libc/sysv/consts/F_OFD_SETLK.s +++ b/libc/sysv/consts/F_OFD_SETLK.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon fcntl,F_OFD_SETLK,37,90,-1,-1,-1,6 +.syscon fcntl,F_OFD_SETLK,37,-1,-1,-1,-1,-1 diff --git a/libc/sysv/consts/F_OFD_SETLKW.s b/libc/sysv/consts/F_OFD_SETLKW.s index c59ff381e..c4a6d7a70 100644 --- a/libc/sysv/consts/F_OFD_SETLKW.s +++ b/libc/sysv/consts/F_OFD_SETLKW.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon fcntl,F_OFD_SETLKW,38,91,-1,-1,-1,7 +.syscon fcntl,F_OFD_SETLKW,38,-1,-1,-1,-1,-1 diff --git a/libc/sysv/consts/TIOCDRAIN.s b/libc/sysv/consts/TIOCDRAIN.s deleted file mode 100644 index 21f9cd93c..000000000 --- a/libc/sysv/consts/TIOCDRAIN.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/consts/syscon.internal.inc" -.syscon termios,TIOCDRAIN,0x5409,0x2000745e,0x2000745e,0x2000745e,0x2000745e,0 diff --git a/libc/sysv/consts/clone.h b/libc/sysv/consts/clone.h index f88c712ff..d70e15e0e 100644 --- a/libc/sysv/consts/clone.h +++ b/libc/sysv/consts/clone.h @@ -1,10 +1,12 @@ #ifndef COSMOPOLITAN_LIBC_SYSV_CONSTS_CLONE_H_ #define COSMOPOLITAN_LIBC_SYSV_CONSTS_CLONE_H_ +#define CSIGNAL 0x000000ff #define CLONE_VM 0x00000100 #define CLONE_FS 0x00000200 #define CLONE_FILES 0x00000400 #define CLONE_SIGHAND 0x00000800 +#define CLONE_PIDFD 0x00001000 #define CLONE_PTRACE 0x00002000 #define CLONE_VFORK 0x00004000 #define CLONE_PARENT 0x00008000 @@ -17,6 +19,12 @@ #define CLONE_DETACHED 0x00400000 #define CLONE_UNTRACED 0x00800000 #define CLONE_CHILD_SETTID 0x01000000 -#define CLONE_STOPPED 0x02000000 +#define CLONE_NEWCGROUP 0x02000000 +#define CLONE_NEWUTS 0x04000000 +#define CLONE_NEWIPC 0x08000000 +#define CLONE_NEWUSER 0x10000000 +#define CLONE_NEWPID 0x20000000 +#define CLONE_NEWNET 0x40000000 +#define CLONE_IO 0x80000000 #endif /* COSMOPOLITAN_LIBC_SYSV_CONSTS_CLONE_H_ */ diff --git a/libc/sysv/consts/f.h b/libc/sysv/consts/f.h index f5a48e19b..53ea7bddf 100644 --- a/libc/sysv/consts/f.h +++ b/libc/sysv/consts/f.h @@ -76,7 +76,7 @@ COSMOPOLITAN_C_END_ #define F_UNLCK SYMBOLIC(F_UNLCK) #define F_WRLCK SYMBOLIC(F_WRLCK) -/* avoid leading #ifdef astray */ +/* avoid leading #ifdef configurations astray */ /* #define F_FULLFSYNC SYMBOLIC(F_FULLFSYNC) */ /* #define F_BARRIERFSYNC SYMBOLIC(F_BARRIERFSYNC) */ /* #define F_OFD_GETLK SYMBOLIC(F_OFD_GETLK) */ diff --git a/libc/sysv/consts/termios.h b/libc/sysv/consts/termios.h index 7d3fe9d5d..b7cbd2080 100644 --- a/libc/sysv/consts/termios.h +++ b/libc/sysv/consts/termios.h @@ -111,7 +111,6 @@ extern const uint64_t TIOCCBRK; extern const uint64_t TIOCCDTR; extern const uint64_t TIOCCHKVERAUTH; extern const uint64_t TIOCCONS; -extern const uint64_t TIOCDRAIN; extern const uint64_t TIOCEXT; extern const uint64_t TIOCFLAG_CLOCAL; extern const uint64_t TIOCFLAG_MDMBUF; @@ -300,7 +299,6 @@ COSMOPOLITAN_C_END_ #define TIOCCDTR SYMBOLIC(TIOCCDTR) #define TIOCCHKVERAUTH SYMBOLIC(TIOCCHKVERAUTH) #define TIOCCONS SYMBOLIC(TIOCCONS) -#define TIOCDRAIN SYMBOLIC(TIOCDRAIN) #define TIOCEXT SYMBOLIC(TIOCEXT) #define TIOCFLAG_CLOCAL SYMBOLIC(TIOCFLAG_CLOCAL) #define TIOCFLAG_MDMBUF SYMBOLIC(TIOCFLAG_MDMBUF) diff --git a/libc/sysv/syscalls.sh b/libc/sysv/syscalls.sh index 39ae8d635..5db4784ca 100755 --- a/libc/sysv/syscalls.sh +++ b/libc/sysv/syscalls.sh @@ -92,7 +92,7 @@ scall __sys_getpeername 0x01f01f08d201f034 globl hidden scall __sys_socketpair 0x0870870872087035 globl hidden scall sys_setsockopt 0x0690690692069036 globl hidden scall sys_getsockopt 0x0760760762076037 globl hidden -scall sys_fork 0x0020020022002039 globl hidden # xnu needs eax&=~-edx bc eax always holds pid and edx is 0 for parent and 1 for child +scall __sys_fork 0x0020020022002039 globl hidden # xnu needs eax&=~-edx bc eax always holds pid and edx is 0 for parent and 1 for child #scall vfork 0x042042042204203a globl # this syscall is from the moon so we implement it by hand in libc/runtime/vfork.S; probably removed from XNU in 12.5 scall sys_posix_spawn 0x1daffffff20f4fff globl hidden # good luck figuring out how xnu defines this scall __sys_execve 0x03b03b03b203b03b globl hidden diff --git a/libc/thread/freebsd.internal.h b/libc/thread/freebsd.internal.h index 53addff10..5134a108d 100644 --- a/libc/thread/freebsd.internal.h +++ b/libc/thread/freebsd.internal.h @@ -11,6 +11,9 @@ * maximum legal range is PID_MAX + 2 (100001) through INT_MAX */ +#define THR_SUSPENDED 1 +#define THR_SYSTEM_SCOPE 2 + #define UMTX_OP_WAIT 2 #define UMTX_OP_WAIT_UINT 11 #define UMTX_OP_WAIT_UINT_PRIVATE 15 diff --git a/libc/thread/posixthread.internal.h b/libc/thread/posixthread.internal.h index 95ed0400d..e4129c819 100644 --- a/libc/thread/posixthread.internal.h +++ b/libc/thread/posixthread.internal.h @@ -16,8 +16,6 @@ COSMOPOLITAN_C_START_ * @fileoverview Cosmopolitan POSIX Thread Internals */ -typedef void (*atfork_f)(void); - // LEGAL TRANSITIONS ┌──> TERMINATED // pthread_create ─┬─> JOINABLE ─┴┬─> DETACHED ──> ZOMBIE // └──────────────┘ @@ -80,6 +78,8 @@ struct PosixThread { struct _pthread_cleanup_buffer *cleanup; }; +typedef void (*atfork_f)(void); + extern struct PosixThread _pthread_main; hidden extern pthread_spinlock_t _pthread_keys_lock; hidden extern uint64_t _pthread_key_usage[(PTHREAD_KEYS_MAX + 63) / 64]; diff --git a/libc/thread/pthread_attr_getscope.c b/libc/thread/pthread_attr_getscope.c index ed264edf6..2556481fe 100644 --- a/libc/thread/pthread_attr_getscope.c +++ b/libc/thread/pthread_attr_getscope.c @@ -18,7 +18,12 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/thread/thread.h" -int pthread_attr_getscope(const pthread_attr_t *a, int *x) { - *x = a->__scope; +/** + * Gets contention scope attribute. + * + * @return 0 on success, or errno on error + */ +int pthread_attr_getscope(const pthread_attr_t *attr, int *contentionscope) { + *contentionscope = attr->__contentionscope; return 0; } diff --git a/libc/thread/pthread_attr_setscope.c b/libc/thread/pthread_attr_setscope.c index e4aaaf856..648198523 100644 --- a/libc/thread/pthread_attr_setscope.c +++ b/libc/thread/pthread_attr_setscope.c @@ -16,9 +16,34 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/dce.h" +#include "libc/errno.h" #include "libc/thread/thread.h" -int pthread_attr_setscope(pthread_attr_t *a, int x) { - a->__scope = x; - return 0; +/** + * Sets contention scope attribute. + * + * @param contentionscope may be one of: + * - `PTHREAD_SCOPE_SYSTEM` to fight the system for resources + * - `PTHREAD_SCOPE_PROCESS` to fight familiar threads for resources + * @return 0 on success, or errno on error + * @raise ENOTSUP if `contentionscope` isn't supported on host OS + * @raise EINVAL if `contentionscope` was invalid + */ +int pthread_attr_setscope(pthread_attr_t *attr, int contentionscope) { + switch (contentionscope) { + case PTHREAD_SCOPE_SYSTEM: + attr->__contentionscope = contentionscope; + return 0; + case PTHREAD_SCOPE_PROCESS: + // Linux almost certainly doesn't support thread scoping + // FreeBSD has THR_SYSTEM_SCOPE but it's not implemented + // OpenBSD pthreads claims support but really ignores it + // NetBSD pthreads claims support, but really ignores it + // XNU sources appear to make no mention of thread scope + // WIN32 documentation says nothing about thread scoping + return ENOTSUP; + default: + return EINVAL; + } } diff --git a/libc/thread/pthread_cleanup_pop.c b/libc/thread/pthread_cleanup_pop.c index b9efb902e..f8bb41e0f 100644 --- a/libc/thread/pthread_cleanup_pop.c +++ b/libc/thread/pthread_cleanup_pop.c @@ -19,10 +19,11 @@ #include "libc/assert.h" #include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" +#include "libc/thread/tls.h" void _pthread_cleanup_pop(struct _pthread_cleanup_buffer *cb, int execute) { - struct PosixThread *pt = (struct PosixThread *)pthread_self(); - _npassert(cb == pt->cleanup); + struct PosixThread *pt = (struct PosixThread *)__get_tls()->tib_pthread; + _unassert(cb == pt->cleanup); pt->cleanup = cb->__prev; if (execute) { cb->__routine(cb->__arg); diff --git a/libc/thread/pthread_cleanup_push.c b/libc/thread/pthread_cleanup_push.c index 125d30c18..dd5428968 100644 --- a/libc/thread/pthread_cleanup_push.c +++ b/libc/thread/pthread_cleanup_push.c @@ -21,7 +21,7 @@ void _pthread_cleanup_push(struct _pthread_cleanup_buffer *cb, void (*routine)(void *), void *arg) { - struct PosixThread *pt = (struct PosixThread *)pthread_self(); + struct PosixThread *pt = (struct PosixThread *)__get_tls()->tib_pthread; cb->__routine = routine; cb->__arg = arg; cb->__prev = pt->cleanup; diff --git a/libc/thread/pthread_create.c b/libc/thread/pthread_create.c index 27429b8f9..84df4dde6 100644 --- a/libc/thread/pthread_create.c +++ b/libc/thread/pthread_create.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/calls/blocksigs.internal.h" #include "libc/calls/calls.h" #include "libc/calls/sched-sysv.internal.h" #include "libc/calls/state.internal.h" @@ -97,7 +98,7 @@ static int PosixThread(void *arg, int tid) { // set long jump handler so pthread_exit can bring control back here if (!setjmp(pt->exiter)) { __get_tls()->tib_pthread = (pthread_t)pt; - sigprocmask(SIG_SETMASK, &pt->sigmask, 0); + _sigsetmask(pt->sigmask); pt->rc = pt->start_routine(pt->arg); // ensure pthread_cleanup_pop(), and pthread_exit() popped cleanup _npassert(!pt->cleanup); @@ -325,12 +326,10 @@ static errno_t pthread_create_impl(pthread_t *thread, errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg) { errno_t rc; - sigset_t blocksigs, oldsigs; __require_tls(); _pthread_zombies_decimate(); - sigfillset(&blocksigs); - _npassert(!sigprocmask(SIG_SETMASK, &blocksigs, &oldsigs)); - rc = pthread_create_impl(thread, attr, start_routine, arg, oldsigs); - _npassert(!sigprocmask(SIG_SETMASK, &oldsigs, 0)); + BLOCK_SIGNALS; + rc = pthread_create_impl(thread, attr, start_routine, arg, _SigMask); + ALLOW_SIGNALS; return rc; } diff --git a/libc/thread/pthread_kill.c b/libc/thread/pthread_kill.c index e6f0ca18a..61e39716d 100644 --- a/libc/thread/pthread_kill.c +++ b/libc/thread/pthread_kill.c @@ -29,6 +29,7 @@ * @raise EAGAIN if `RLIMIT_SIGPENDING` was exceeded * @raise EINVAL if `tid` or `sig` was invalid * @raise EPERM if permission was denied + * @asyncsignalsafe */ int pthread_kill(pthread_t thread, int sig) { int rc, e = errno; diff --git a/libc/thread/pthread_mutexattr_gettype.c b/libc/thread/pthread_mutexattr_gettype.c index 6138568e9..5d1e9a8d7 100644 --- a/libc/thread/pthread_mutexattr_gettype.c +++ b/libc/thread/pthread_mutexattr_gettype.c @@ -23,7 +23,6 @@ * * @param type will be set to one of these on success * - `PTHREAD_MUTEX_NORMAL` - * - `PTHREAD_MUTEX_DEFAULT` * - `PTHREAD_MUTEX_RECURSIVE` * - `PTHREAD_MUTEX_ERRORCHECK` * @return 0 on success, or error on failure diff --git a/libc/thread/pthread_self.c b/libc/thread/pthread_self.c index 2152a3482..250cc0391 100644 --- a/libc/thread/pthread_self.c +++ b/libc/thread/pthread_self.c @@ -22,6 +22,7 @@ /** * Returns current POSIX thread. + * @asyncsignalsafe */ pthread_t pthread_self(void) { pthread_t t; diff --git a/libc/thread/pthread_sigmask.c b/libc/thread/pthread_sigmask.c index 78a1dd185..03093c8dc 100644 --- a/libc/thread/pthread_sigmask.c +++ b/libc/thread/pthread_sigmask.c @@ -23,6 +23,7 @@ * Examines and/or changes blocked signals on current thread. * * @return 0 on success, or errno on error + * @asyncsignalsafe */ int pthread_sigmask(int how, const sigset_t *set, sigset_t *old) { int rc, e = errno; diff --git a/libc/thread/thread.h b/libc/thread/thread.h index 6f2d5eda9..b38171bbc 100644 --- a/libc/thread/thread.h +++ b/libc/thread/thread.h @@ -31,6 +31,9 @@ #define PTHREAD_CANCEL_DEFERRED 0 #define PTHREAD_CANCEL_ASYNCHRONOUS 1 +#define PTHREAD_SCOPE_SYSTEM 0 +#define PTHREAD_SCOPE_PROCESS 1 + #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ @@ -89,7 +92,7 @@ typedef struct pthread_attr_s { char __inheritsched; int __schedparam; int __schedpolicy; - int __scope; + int __contentionscope; unsigned __guardsize; unsigned __stacksize; char *__stackaddr; diff --git a/libc/time/iso8601.c b/libc/time/iso8601.c index 8a9b40d60..ad1113a95 100644 --- a/libc/time/iso8601.c +++ b/libc/time/iso8601.c @@ -16,37 +16,78 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" #include "libc/time/struct/tm.h" #include "libc/time/time.h" /** * Converts timestamp to ISO-8601 formatted string. + * + * For example: + * + * char *GetZuluTime(void) { + * char *p; + * struct tm tm; + * struct timespec ts; + * static _Thread_local int64_t last; + * static _Thread_local char str[21]; + * clock_gettime(0, &ts); + * if (ts.tv_sec != last) { + * gmtime_r(&ts.tv_sec, &tm); + * last = ts.tv_sec; + * stpcpy(iso8601(str, &tm), "Z"); + * } + * return str; + * } + * + * Will return timestamps that look like: + * + * 2020-01-01T13:14:15Z + * + * The generated timestamp is always exactly 19 characters long. It is + * also always nul terminated too. + * + * This function defines no failure conditions. The results of passing + * timestamp values outside of the appropriate intervals is undefined. + * + * @param p is buffer with at least 20 bytes + * @param tm has valid gmtime_r() or localtime_r() output + * @return pointer to nul terminator within `p`, cf. stpcpy() + * @see iso8601us() for microsecond timestamps + * @asyncsignalsafe + * @threadsafe */ char *iso8601(char p[hasatleast 20], struct tm *tm) { int x; x = tm->tm_year + 1900; + _unassert(0 <= x && x <= 9999); *p++ = '0' + x / 1000; *p++ = '0' + x / 100 % 10; *p++ = '0' + x / 10 % 10; *p++ = '0' + x % 10; *p++ = '-'; x = tm->tm_mon + 1; + _unassert(1 <= x && x <= 12); *p++ = '0' + x / 10; *p++ = '0' + x % 10; *p++ = '-'; x = tm->tm_mday; + _unassert(1 <= x && x <= 31); *p++ = '0' + x / 10; *p++ = '0' + x % 10; *p++ = 'T'; x = tm->tm_hour; + _unassert(0 <= x && x <= 23); *p++ = '0' + x / 10; *p++ = '0' + x % 10; *p++ = ':'; x = tm->tm_min; + _unassert(0 <= x && x <= 59); *p++ = '0' + x / 10; *p++ = '0' + x % 10; *p++ = ':'; x = tm->tm_sec; + _unassert(0 <= x && x <= 60); *p++ = '0' + x / 10; *p++ = '0' + x % 10; *p = 0; diff --git a/libc/time/iso8601us.c b/libc/time/iso8601us.c new file mode 100644 index 000000000..e993be55b --- /dev/null +++ b/libc/time/iso8601us.c @@ -0,0 +1,80 @@ +/*-*- 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/assert.h" +#include "libc/time/struct/tm.h" +#include "libc/time/time.h" + +/** + * Converts timestamp to ISO-8601 formatted string. + * + * For example: + * + * char *GetTimestamp(void) { + * struct timespec ts; + * static _Thread_local struct tm tm; + * static _Thread_local int64_t last; + * static _Thread_local char str[27]; + * clock_gettime(0, &ts); + * if (ts.tv_sec != last) { + * localtime_r(&ts.tv_sec, &tm); + * last = ts.tv_sec; + * } + * iso8601us(str, &tm, ts.tv_nsec); + * return str; + * } + * + * Will return timestamps that look like: + * + * 2020-01-01T13:14:15.123456 + * + * The generated timestamp is always exactly 26 characters long. It is + * also always nul terminated too. + * + * This function defines no failure conditions. The results of passing + * timestamp, or nanosecond values outside their appropriate intervals + * is undefined. + * + * This goes 19x faster than strftime(). + * + * iso8601 l: 21c 7ns + * iso8601us l: 39c 13ns + * strftime l: 779c 252ns + * + * @param p is buffer with at least 20 bytes + * @param tm has valid gmtime_r() or localtime_r() output + * @param ns is nanosecond value associated with timestamp + * @return pointer to nul terminator within `p`, cf. stpcpy() + * @see iso8601() if microsecond resolution isn't desirable + * @asyncsignalsafe + * @threadsafe + */ +char *iso8601us(char p[hasatleast 27], struct tm *tm, long ns) { + int x; + p = iso8601(p, tm); + _unassert(0 <= ns && ns < 1000000000); + *p++ = '.'; + *p++ = '0' + ns / 100000000; + *p++ = '0' + ns / 10000000 % 10; + *p++ = '0' + ns / 1000000 % 10; + *p++ = '0' + ns / 100000 % 10; + *p++ = '0' + ns / 10000 % 10; + *p++ = '0' + ns / 1000 % 10; + *p = 0; + return p; +} diff --git a/libc/time/struct/tm.h b/libc/time/struct/tm.h index b8e411132..c682aec01 100644 --- a/libc/time/struct/tm.h +++ b/libc/time/struct/tm.h @@ -20,6 +20,7 @@ struct tm { char *asctime(const struct tm *); char *asctime_r(const struct tm *, char[hasatleast 26]); char *iso8601(char[hasatleast 20], struct tm *); +char *iso8601us(char[hasatleast 27], struct tm *, long); char *strptime(const char *, const char *, struct tm *); int64_t mktime(struct tm *); int64_t timegm(struct tm *); diff --git a/libc/time/time.mk b/libc/time/time.mk index 840d3a4ca..dec94b996 100644 --- a/libc/time/time.mk +++ b/libc/time/time.mk @@ -62,7 +62,10 @@ o/$(MODE)/libc/time/localtime.o: private \ -fdata-sections \ -ffunction-sections -o/$(MODE)/libc/time/now.o: private \ +# we need -O3 because: +# we're dividing by constants +o/$(MODE)/libc/time/iso8601.o \ +o/$(MODE)/libc/time/iso8601us.o: private \ OVERRIDE_CFLAGS += \ -O3 diff --git a/libc/time/xiso8601.c b/libc/time/xiso8601.c index 3b748de9b..f8b05d14f 100644 --- a/libc/time/xiso8601.c +++ b/libc/time/xiso8601.c @@ -27,9 +27,7 @@ #include "libc/time/time.h" #include "libc/x/x.h" -/** - * @fileoverview Timestamps in One True Format w/o toil. - */ +// TODO(jart): DELETE static char *xiso8601_impl(struct timespec *opt_ts, int sswidth) { char *p; diff --git a/libc/intrin/intrin.h b/libc/zipos/.cosmo old mode 100755 new mode 100644 similarity index 100% rename from libc/intrin/intrin.h rename to libc/zipos/.cosmo diff --git a/libc/zipos/open.c b/libc/zipos/open.c index 333fe9814..943fda568 100644 --- a/libc/zipos/open.c +++ b/libc/zipos/open.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/calls/blocksigs.internal.h" #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/calls/state.internal.h" @@ -188,13 +189,14 @@ static int __zipos_load(struct Zipos *zipos, size_t cf, unsigned flags, * Loads compressed file from αcτµαlly pδrταblε εxεcµταblε object store. * * @param uri is obtained via __zipos_parseuri() - * @asyncsignalsafe (todo) + * @asyncsignalsafe * @threadsafe */ int __zipos_open(const struct ZiposUri *name, unsigned flags, int mode) { int rc; ssize_t cf; struct Zipos *zipos; + BLOCK_SIGNALS; if ((flags & O_ACCMODE) == O_RDONLY) { if ((zipos = __zipos_get())) { if ((cf = __zipos_find(zipos, name)) != -1) { @@ -208,5 +210,6 @@ int __zipos_open(const struct ZiposUri *name, unsigned flags, int mode) { } else { rc = einval(); } + ALLOW_SIGNALS; return rc; } diff --git a/libc/zipos/zipos.S b/libc/zipos/zipos.S index acb603e11..71cb51978 100644 --- a/libc/zipos/zipos.S +++ b/libc/zipos/zipos.S @@ -19,8 +19,8 @@ #include "libc/macros.internal.h" // static_yoink this symbol for open(/zip/...) support. - zip_uri_support = 0 - .globl zip_uri_support + zipos = 0 + .globl zipos .yoink __zip_end .yoink __zip_start @@ -34,3 +34,10 @@ .yoink __zipos_read .yoink __zipos_stat .yoink __zipos_notat + +// TODO(jart): why does corruption happen when zip has no assets? + .yoink .cosmo + +// deprecated: use STATIC_YOINK("zipos") + zip_uri_support = 0 + .globl zip_uri_support diff --git a/libc/zipos/zipos.mk b/libc/zipos/zipos.mk index a7d234281..0632b4a0d 100644 --- a/libc/zipos/zipos.mk +++ b/libc/zipos/zipos.mk @@ -20,7 +20,8 @@ LIBC_ZIPOS_A_SRCS = \ LIBC_ZIPOS_A_OBJS = \ $(LIBC_ZIPOS_A_SRCS_S:%.S=o/$(MODE)/%.o) \ - $(LIBC_ZIPOS_A_SRCS_C:%.c=o/$(MODE)/%.o) + $(LIBC_ZIPOS_A_SRCS_C:%.c=o/$(MODE)/%.o) \ + o/$(MODE)/libc/zipos/.cosmo.zip.o LIBC_ZIPOS_A_CHECKS = \ $(LIBC_ZIPOS_A).pkg \ @@ -51,6 +52,10 @@ $(LIBC_ZIPOS_A).pkg: \ $(LIBC_ZIPOS_A_OBJS) \ $(foreach zipos,$(LIBC_ZIPOS_A_DIRECTDEPS),$($(zipos)_A).pkg) +o/$(MODE)/libc/zipos/.cosmo.zip.o: private \ + ZIPOBJ_FLAGS += \ + -B + LIBC_ZIPOS_LIBS = $(foreach zipos,$(LIBC_ZIPOS_ARTIFACTS),$($(zipos))) LIBC_ZIPOS_SRCS = $(foreach zipos,$(LIBC_ZIPOS_ARTIFACTS),$($(zipos)_SRCS)) LIBC_ZIPOS_HDRS = $(foreach zipos,$(LIBC_ZIPOS_ARTIFACTS),$($(zipos)_HDRS)) diff --git a/net/http/http.h b/net/http/http.h index eb5e8d9d0..de3ab7e09 100644 --- a/net/http/http.h +++ b/net/http/http.h @@ -214,6 +214,7 @@ int ParseForwarded(const char *, size_t, uint32_t *, uint16_t *); bool IsMimeType(const char *, size_t, const char *); ssize_t Unchunk(struct HttpUnchunker *, char *, size_t, size_t *); const char *FindContentType(const char *, size_t); +bool IsNoCompressExt(const char *, size_t); char *FoldHeader(struct HttpMessage *, char *, int, size_t *); COSMOPOLITAN_C_END_ diff --git a/net/http/http.mk b/net/http/http.mk index 470883106..fbe09425c 100644 --- a/net/http/http.mk +++ b/net/http/http.mk @@ -57,6 +57,8 @@ o/$(MODE)/net/http/istestnetip.o: private \ OVERRIDE_CFLAGS += \ -Os +# we need -O3 because: +# we're dividing by constants o/$(MODE)/net/http/formathttpdatetime.o: private\ OVERRIDE_CFLAGS += \ -O3 diff --git a/tool/build/lib/isnocompressext.c b/net/http/isnocompressext.c similarity index 94% rename from tool/build/lib/isnocompressext.c rename to net/http/isnocompressext.c index 3f1b60be8..12d49b19d 100644 --- a/tool/build/lib/isnocompressext.c +++ b/net/http/isnocompressext.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ 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 │ @@ -21,7 +21,7 @@ #include "libc/macros.internal.h" #include "libc/str/str.h" #include "libc/str/tab.internal.h" -#include "tool/build/lib/isnocompressext.h" +#include "net/http/http.h" static const char kNoCompressExts[][8] = { "bz2", // @@ -56,13 +56,14 @@ static bool BisectNoCompressExts(uint64_t ext) { } bool IsNoCompressExt(const char *p, size_t n) { - int c; + int c, i; uint64_t w; if (n == -1) n = p ? strlen(p) : 0; if (n) { - for (w = 0; n--;) { + for (i = w = 0; n--;) { c = p[n] & 255; if (c == '.') break; + if (++i > 8) return false; w <<= 8; w |= kToLower[c]; } diff --git a/net/turfwar/.init.lua b/net/turfwar/.init.lua new file mode 100644 index 000000000..deef6b15a --- /dev/null +++ b/net/turfwar/.init.lua @@ -0,0 +1,46 @@ +-- reverse proxy for turfwar + +ProgramPort(443) +ProgramTokenBucket() + +RELAY_HEADERS_TO_CLIENT = { + 'Access-Control-Allow-Origin', + 'Cache-Control', + 'Connection', + 'Content-Encoding', + 'Content-Type', + 'Last-Modified', + 'Referrer-Policy', + 'Vary', +} + +function OnWorkerStart() + assert(unix.unveil(nil, nil)) + assert(unix.pledge("stdio inet", nil, unix.PLEDGE_PENALTY_RETURN_EPERM)) +end + +function OnHttpRequest() + local status, headers, body = + Fetch('http://127.0.0.1' .. EscapePath(GetPath()), + {method = GetMethod(), + headers = { + ['Accept'] = GetHeader('Accept'), + ['Accept-Encoding'] = GetHeader('Accept-Encoding'), + ['CF-IPCountry'] = GetHeader('CF-IPCountry'), + ['If-Modified-Since'] = GetHeader('If-Modified-Since'), + ['Referer'] = GetHeader('Referer'), + ['Sec-CH-UA-Platform'] = GetHeader('Sec-CH-UA-Platform'), + ['User-Agent'] = GetHeader('User-Agent'), + ['X-Forwarded-For'] = FormatIp(GetClientAddr())}}) + if status then + SetStatus(status) + for k,v in pairs(RELAY_HEADERS_TO_CLIENT) do + SetHeader(v, headers[v]) + end + Write(body) + else + err = headers + Log(kLogError, "proxy failed %s" % {err}) + ServeError(503) + end +end diff --git a/libc/intrin/getenv.greg.c b/net/turfwar/blackhole.c similarity index 59% rename from libc/intrin/getenv.greg.c rename to net/turfwar/blackhole.c index b552280c4..9f38ca0d1 100644 --- a/libc/intrin/getenv.greg.c +++ b/net/turfwar/blackhole.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ Copyright 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 │ @@ -16,62 +16,55 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/assert.h" -#include "libc/intrin/strace.internal.h" -#include "libc/dce.h" +#include "libc/calls/calls.h" #include "libc/errno.h" -#include "libc/log/libfatal.internal.h" +#include "libc/intrin/bits.h" +#include "libc/intrin/kprintf.h" #include "libc/runtime/runtime.h" +#include "libc/sock/sock.h" +#include "libc/sock/struct/sockaddr.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/af.h" +#include "libc/sysv/consts/ex.h" +#include "libc/sysv/consts/sock.h" +#include "net/http/http.h" -forceinline int Identity(int c) { - return c; -} +int main(int argc, char *argv[]) { -forceinline int ToUpper(int c) { - return 'a' <= c && c <= 'z' ? c - ('a' - 'A') : c; -} + if (argc < 2) { + kprintf("usage: blackhole IP...\n"); + return EX_USAGE; + } -forceinline char *GetEnv(const char *s, int xlat(int)) { - char **p; - size_t i, j; - if ((p = environ)) { - for (i = 0; p[i]; ++i) { - for (j = 0;; ++j) { - if (!s[j]) { - if (p[i][j] == '=') { - return p[i] + j + 1; - } - break; - } - if (xlat(s[j]) != xlat(p[i][j])) { - break; - } + int fd; + struct sockaddr_un addr = { + AF_UNIX, + "/var/run/blackhole.sock", + }; + if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) { + kprintf("error: socket(AF_UNIX) failed: %s\n", strerror(errno)); + return 3; + } + if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + kprintf("error: connect(%#s) failed: %s\n", addr.sun_path, strerror(errno)); + return 4; + } + + int rc = 0; + for (int i = 1; i < argc; ++i) { + int64_t ip; + char buf[4]; + if ((ip = ParseIp(argv[i], -1)) != -1) { + WRITE32BE(buf, ip); + if (write(fd, buf, 4) == -1) { + kprintf("error: write() failed: %s\n", strerror(errno)); + rc |= 2; } + } else { + kprintf("error: bad ipv4 address: %s\n", argv[i]); + rc |= 1; } } - return 0; -} -/** - * Returns value of environment variable, or NULL if not found. - * - * Environment variables can store empty string on Unix but not Windows. - * - * @note should not be used after __cxa_finalize() is called - */ -char *getenv(const char *s) { - char *r; - if (!s) return 0; - if (!IsWindows()) { - r = GetEnv(s, Identity); - } else { - r = GetEnv(s, ToUpper); - } -#if SYSDEBUG - if (!(s[0] == 'T' && s[1] == 'Z' && !s[2])) { - // TODO(jart): memoize TZ or something - STRACE("getenv(%#s) → %#s", s, r); - } -#endif - return r; + return rc; } diff --git a/net/turfwar/blackholed.c b/net/turfwar/blackholed.c new file mode 100644 index 000000000..7a3e09fda --- /dev/null +++ b/net/turfwar/blackholed.c @@ -0,0 +1,448 @@ +/*-*- 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/assert.h" +#include "libc/calls/blocksigs.internal.h" +#include "libc/calls/calls.h" +#include "libc/calls/struct/sigaction.h" +#include "libc/calls/struct/sigset.h" +#include "libc/calls/struct/timespec.h" +#include "libc/dce.h" +#include "libc/errno.h" +#include "libc/fmt/conv.h" +#include "libc/fmt/itoa.h" +#include "libc/intrin/bits.h" +#include "libc/intrin/kprintf.h" +#include "libc/intrin/midpoint.h" +#include "libc/intrin/safemacros.internal.h" +#include "libc/mem/mem.h" +#include "libc/mem/sortedints.internal.h" +#include "libc/runtime/runtime.h" +#include "libc/sock/sock.h" +#include "libc/sock/struct/sockaddr.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/af.h" +#include "libc/sysv/consts/clock.h" +#include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/ok.h" +#include "libc/sysv/consts/sa.h" +#include "libc/sysv/consts/sig.h" +#include "libc/sysv/consts/sock.h" +#include "libc/sysv/consts/timer.h" +#include "libc/time/struct/tm.h" +#include "third_party/getopt/getopt.h" +#include "third_party/musl/passwd.h" + +#define LOG(FMT, ...) \ + kprintf("%s %s:%d] " FMT "\n", GetTimestamp(), __FILE__, __LINE__, \ + ##__VA_ARGS__) + +#define DEFAULT_LOGNAME "/var/log/blackhole.log" +#define DEFAULT_PIDNAME "/var/run/blackhole.pid" +#define DEFAULT_SOCKNAME "/var/run/blackhole.sock" +#define GETOPTS "L:S:P:M:G:dh" +#define USAGE \ + "\ +Usage: blackholed [-hdLPSMG]\n\ + -h help\n\ + -d daemonize\n\ + -L PATH log file name (default: " DEFAULT_LOGNAME ")\n\ + -P PATH pid file name (default: " DEFAULT_PIDNAME ")\n\ + -S PATH socket file name (default: " DEFAULT_SOCKNAME ")\n\ + -M MODE socket mode bits (default: 0777)\n\ + -G GROUP socket group name or gid (default: n/a)\n\ + --assimilate change executable header to native format\n\ + --ftrace function call tracing\n\ + --strace system call tracing\n\ +\n\ +Usage:\n\ + sudo blackholed -d # run daemon\n\ + blackhole 1.2.3.4 # anyone can securely ban ips\n\ +\n\ +Protocol:\n\ + Send a 4 byte datagram to the unix socket file containing\n\ + the IPv4 address you want banned encoded using big endian\n\ + a.k.a. network byte order. We ignore these ips: 0.0.0.0/8\n\ + and 127.0.0.0/8 so sending 0 to the socket is a good test\n" + +#define LINUX_DOCS \ + "\n\ +Linux Requirements:\n\ + sudo modprobe ip_tables\n\ + sudo echo ip_tables >>/etc/modules\n\ +\n\ +Administration Notes:\n\ + This program inserts IP bans into iptables raw prerouting, so\n\ + the kernel won't track the TCP connections of threat actors.\n\ + If you restart this program, then you should run\n\ + sudo iptables -t raw -F\n\ + to clear the IP blocks. It's a good idea to have a cron job\n\ + restart this daemon and clear the raw table daily. Use the\n\ + sudo iptables -t raw -L -vn\n\ + command to list the IP addresses that have been blocked.\n\ +\n" + +#define BSD_DOCS \ + "\n\ +BSD Requirements:\n\ + kldload pf\n\ + echo 'table persist' >>/etc/pf.conf\n\ + echo 'block on em0 from to any' >>/etc/pf.conf\n\ + echo 'pf_enable=\"YES\"' >>/etc/rc.conf\n\ + echo 'pf_rules=\"/etc/pf.conf\"' >>/etc/rc.conf\n\ + /etc/rc.d/pf start\n\ + pfctl -t badhosts -T add 1.2.3.4\n\ + pfctl -t badhosts -T show\n\ +\n\ +Administration Notes:\n\ + If you restart this program, then you should run\n\ + pfctl -t badhosts -T flush\n\ + to clear the IP blocks. It's a good idea to have a cron job\n\ + restart this daemon and clear the raw table daily. Use the\n\ + pfctl -t badhosts -T show\n\ + command to list the IP addresses that have been blocked.\n\ +\n\ +" + +int g_logfd; +int g_sockmode; +bool g_daemonize; +uint32_t *g_myips; +const char *g_group; +const char *g_pfctl; +const char *g_logname; +const char *g_pidname; +const char *g_sockname; +const char *g_iptables; +sig_atomic_t g_shutdown; +struct SortedInts g_blocked; + +static wontreturn void ShowUsage(int fd, int rc) { + write(fd, USAGE, sizeof(USAGE) - 1); + if (IsLinux()) write(fd, LINUX_DOCS, sizeof(LINUX_DOCS) - 1); + if (IsBsd()) write(fd, BSD_DOCS, sizeof(BSD_DOCS) - 1); + _Exit(rc); +} + +static void GetOpts(int argc, char *argv[]) { + int opt; + g_sockmode = 0777; + g_pidname = DEFAULT_PIDNAME; + g_logname = DEFAULT_LOGNAME; + g_sockname = DEFAULT_SOCKNAME; + while ((opt = getopt(argc, argv, GETOPTS)) != -1) { + switch (opt) { + case 'd': + g_daemonize = true; + break; + case 'S': + g_sockname = optarg; + break; + case 'L': + g_logname = emptytonull(optarg); + break; + case 'P': + g_pidname = emptytonull(optarg); + break; + case 'G': + g_group = emptytonull(optarg); + break; + case 'M': + g_sockmode = strtol(optarg, 0, 8) & 0777; + break; + case 'h': + ShowUsage(1, 0); + default: + ShowUsage(2, 64); + } + } +} + +char *GetTimestamp(void) { + struct timespec ts; + static struct tm tm; + static int64_t last; + static char str[27]; + clock_gettime(0, &ts); + if (ts.tv_sec != last) { + localtime_r(&ts.tv_sec, &tm); + last = ts.tv_sec; + } + iso8601us(str, &tm, ts.tv_nsec); + return str; +} + +void OnTerm(int sig) { + char tmp[15]; + LOG("got %s", strsignal_r(sig, tmp)); + g_shutdown = sig; +} + +char *FormatIp(uint32_t ip) { + static char ipbuf[16]; + ksnprintf(ipbuf, sizeof(ipbuf), "%hhu.%hhu.%hhu.%hhu", ip >> 24, ip >> 16, + ip >> 8, ip); + return ipbuf; +} + +void BlockIp(uint32_t ip) { + if (!vfork()) { + if (g_iptables) { + execve(g_iptables, + (char *const[]){ + "iptables", // + "-t", "raw", // + "-I", "PREROUTING", // + "-s", FormatIp(ip), // + "-j", "DROP", // + 0, // + }, + (char *const[]){0}); + } else if (g_pfctl) { + execve(g_pfctl, + (char *const[]){ + "pfctl", // + "-t", "badhosts", // + "-T", "add", // + FormatIp(ip), // + 0, // + }, + (char *const[]){0}); + } + _Exit(127); + } +} + +void RequireRoot(void) { + if (geteuid()) { + kprintf("error: need root privileges\n"); + ShowUsage(2, 2); + } +} + +void ListenForTerm(void) { + struct sigaction sa = {.sa_handler = OnTerm}; + _npassert(!sigaction(SIGTERM, &sa, 0)); + _npassert(!sigaction(SIGHUP, &sa, 0)); + _npassert(!sigaction(SIGINT, &sa, 0)); +} + +void AutomaticallyHarvestZombies(void) { + struct sigaction sa = {.sa_handler = SIG_IGN, .sa_flags = SA_NOCLDWAIT}; + _npassert(!sigaction(SIGCHLD, &sa, 0)); +} + +void FindFirewall(void) { + if (!access("/sbin/iptables", X_OK)) { + g_iptables = "/sbin/iptables"; + } else if (!access("/usr/sbin/iptables", X_OK)) { + g_iptables = "/usr/sbin/iptables"; + } else if (!access("/sbin/pfctl", X_OK)) { + g_pfctl = "/sbin/pfctl"; + } else { + kprintf("error: could not find `iptables` or `pfctl` command\n"); + ShowUsage(2, 3); + } + errno = 0; +} + +void OpenLog(void) { + if (!g_logname) return; + if (!g_daemonize) return; + if ((g_logfd = open(g_logname, O_WRONLY | O_APPEND | O_CREAT, 0644)) == -1) { + kprintf("error: open(%#s) failed: %s\n", g_logname, strerror(errno)); + ShowUsage(2, 5); + } +} + +void Daemonize(void) { + if (g_daemonize && daemon(false, false)) { + kprintf("error: daemon() failed: %s\n", strerror(errno)); + ShowUsage(2, 4); + } +} + +void UseLog(void) { + _npassert(dup2(g_logfd, 2) == 2); + if (g_logfd != 2) { + _npassert(!close(g_logfd)); + } +} + +void UninterruptibleSleep(int ms) { + struct timespec ts = + _timespec_add(_timespec_real(), _timespec_frommillis(ms)); + while (clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &ts, 0)) errno = 0; +} + +void Unlink(const char *path) { + if (!path) return; + if (!unlink(path)) { + LOG("deleted %s", path); + } else { + if (errno != ENOENT) { + LOG("error: unlink(%#s) failed: %s", path, strerror(errno)); + } + errno = 0; + } +} + +void WritePid(void) { + ssize_t rc; + int fd, pid; + char buf[12] = {0}; + if (!g_pidname) return; + if ((fd = open(g_pidname, O_RDWR | O_CREAT, 0644)) == -1) { + LOG("error: open(%#s) failed: %s", g_pidname, strerror(errno)); + _Exit(4); + } + _npassert((rc = pread(fd, buf, 11, 0)) != -1); + if (rc) { + pid = atoi(buf); + LOG("killing old blackholed process %d", pid); + if (!kill(pid, SIGTERM)) { + UninterruptibleSleep(100); + if (kill(pid, SIGKILL)) { + if (errno != ESRCH) { + LOG("kill -KILL %s failed: %s", pid, strerror(errno)); + } + errno = 0; + } + } else { + if (errno != ESRCH) { + LOG("kill -TERM %d failed: %s", pid, strerror(errno)); + } + errno = 0; + } + } + FormatInt32(buf, getpid()); + _npassert(!ftruncate(fd, 0)); + _npassert((rc = pwrite(fd, buf, strlen(buf), 0)) == strlen(buf)); + _npassert(!close(fd)); +} + +bool IsMyIp(uint32_t ip) { + uint32_t *p; + for (p = g_myips; *p; ++p) { + if (ip == *p) { + return true; + } + } + return false; +} + +int main(int argc, char *argv[]) { + + if (closefrom(3)) + for (int i = 3; i < 256; ++i) // + close(i); + + GetOpts(argc, argv); + RequireRoot(); + FindFirewall(); + OpenLog(); + Daemonize(); + UseLog(); + WritePid(); + Unlink(g_sockname); + + if (!(g_myips = GetHostIps())) { + LOG("failed to get host network interface addresses: %s", strerror(errno)); + } + + int server; + struct sockaddr_un addr = {AF_UNIX}; + strlcpy(addr.sun_path, g_sockname, sizeof(addr.sun_path)); + if ((server = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) { + LOG("error: socket(AF_UNIX) failed: %s", strerror(errno)); + _Exit(3); + } + if (bind(server, (struct sockaddr *)&addr, sizeof(addr))) { + LOG("error: bind(%s) failed: %s", g_sockname, strerror(errno)); + _Exit(4); + } + if (chmod(g_sockname, g_sockmode)) { + LOG("error: chmod(%s, %o) failed: %s", g_sockname, g_sockmode, + strerror(errno)); + _Exit(5); + } + if (g_group) { + int gid; + struct group *g; + if (isdigit(*g_group)) { + gid = atoi(g_group); + } else if ((g = getgrnam(g_group))) { + gid = g->gr_gid; + } else { + LOG("error: group %s not found: %s", g_group, strerror(errno)); + _Exit(6); + } + if (chown(g_sockname, -1, gid)) { + LOG("error: chmod(%s, -1, %o) failed: %s", g_sockname, g_sockmode, + strerror(errno)); + _Exit(7); + } + } + + AutomaticallyHarvestZombies(); + ListenForTerm(); + + while (!g_shutdown) { + ssize_t rc; + uint32_t ip; + char msg[16]; + + if (!(rc = read(server, msg, sizeof(msg)))) { + LOG("error: impossible eof", strerror(errno)); + _Exit(6); + } else if (rc == -1) { + if (errno == EINTR) { + errno = 0; + continue; + } + LOG("error: read failed: %s", strerror(errno)); + continue; + } else if (rc != 4) { + LOG("error: read unexpected size of %ld: %s", rc, strerror(errno)); + continue; + } + + BLOCK_SIGNALS; + + if ((ip = READ32BE(msg))) { + if (IsMyIp(ip) || // nics + (ip & 0xff000000) == 0x00000000 || // 0.0.0.0/8 + (ip & 0xff000000) == 0x7f000000) { // 127.0.0.0/8 + LOG("won't block %s", FormatIp(ip)); + } else if (InsertInt(&g_blocked, ip, true)) { + BlockIp(ip); + LOG("blocked %s", FormatIp(ip)); + } else { + LOG("already blocked %s", FormatIp(ip)); + } + } + + ALLOW_SIGNALS; + } + + if (g_shutdown == SIGINT || // + g_shutdown == SIGHUP) { + Unlink(g_sockname); + Unlink(g_pidname); + } +} diff --git a/net/turfwar/blackholed.sh b/net/turfwar/blackholed.sh new file mode 100755 index 000000000..053defc1a --- /dev/null +++ b/net/turfwar/blackholed.sh @@ -0,0 +1,5 @@ +#!/bin/sh +make -j16 o//net/turfwar/blackholed.elf && +sudo chown root o//net/turfwar/blackholed.elf && +sudo chmod 06755 o//net/turfwar/blackholed.elf && +exec o//net/turfwar/blackholed.elf diff --git a/net/turfwar/turfwar.c b/net/turfwar/turfwar.c index 70f87c5fa..43a71df8c 100644 --- a/net/turfwar/turfwar.c +++ b/net/turfwar/turfwar.c @@ -90,7 +90,7 @@ #define PORT 8080 // default server listening port #define CPUS 64 // number of cpus to actually use -#define WORKERS 100 // size of http client thread pool +#define WORKERS 500 // size of http client thread pool #define SUPERVISE_MS 1000 // how often to stat() asset files #define KEEPALIVE_MS 60000 // max time to keep idle conn open #define MELTALIVE_MS 2000 // panic keepalive under heavy load @@ -108,7 +108,7 @@ #define BATCH_MAX 64 // max claims to insert per transaction #define NICK_MAX 40 // max length of user nickname string #define TB_INTERVAL 1000 // millis between token replenishes -#define TB_CIDR 22 // token bucket cidr specificity +#define TB_CIDR 24 // token bucket cidr specificity #define SOCK_MAX 100 // max length of socket queue #define MSG_BUF 512 // small response lookaside @@ -233,6 +233,14 @@ struct Asset { char lastmodified[32]; }; +struct Blackhole { + struct sockaddr_un addr; + int fd; +} g_blackhole = {{ + AF_UNIX, + "/var/run/blackhole.sock", +}}; + // cli flags bool g_integrity; bool g_daemonize; @@ -248,6 +256,7 @@ atomic_int g_connections; nsync_note g_shutdown[3]; // whitebox metrics +atomic_long g_banned; atomic_long g_accepts; atomic_long g_dbfails; atomic_long g_proxied; @@ -401,6 +410,19 @@ int DbPrepare(sqlite3 *db, sqlite3_stmt **stmt, const char *sql) { return sqlite3_prepare_v2(db, sql, -1, stmt, 0); } +bool Blackhole(uint32_t ip) { + char buf[4]; + WRITE32BE(buf, ip); + if (sendto(g_blackhole.fd, buf, 4, 0, (struct sockaddr *)&g_blackhole.addr, + sizeof(g_blackhole.addr)) == 4) { + return true; + } else { + kprintf("error: sendto(/var/run/blackhole.sock) failed: %s\n", + strerror(errno)); + return false; + } +} + // validates name registration validity bool IsValidNick(const char *s, size_t n) { size_t i; @@ -674,10 +696,11 @@ void ServeStatusz(int client, char *outbuf) { g_messages / MAX(1, _timespec_sub(now, g_started).tv_sec)); p = Statusz(p, "started", g_started.tv_sec); p = Statusz(p, "now", now.tv_sec); + p = Statusz(p, "messages", g_messages); p = Statusz(p, "connections", g_connections); + p = Statusz(p, "banned", g_banned); p = Statusz(p, "workers", g_workers); p = Statusz(p, "accepts", g_accepts); - p = Statusz(p, "messages", g_messages); p = Statusz(p, "dbfails", g_dbfails); p = Statusz(p, "proxied", g_proxied); p = Statusz(p, "memfails", g_memfails); @@ -759,8 +782,8 @@ void *ListenWorker(void *arg) { } if (!AddClient(&g_clients, &client, WaitFor(ACCEPT_DEADLINE_MS))) { ++g_rejected; - LOG("502 Accept Queue Full\n"); - Write(client.sock, "HTTP/1.1 502 Accept Queue Full\r\n" + LOG("503 Accept Queue Full\n"); + Write(client.sock, "HTTP/1.1 503 Accept Queue Full\r\n" "Content-Type: text/plain\r\n" "Connection: close\r\n" "\r\n" @@ -834,6 +857,9 @@ void *HttpWorker(void *arg) { ++g_messages; ++g_worker[id].msgcount; + ipv6 = false; + ip = clientip; + // get client address from frontend if (HasHeader(kHttpXForwardedFor)) { if (!IsLoopbackIp(clientip) && // @@ -861,17 +887,21 @@ void *HttpWorker(void *arg) { ip = clientip; ++g_unproxied; } + ksnprintf(ipbuf, sizeof(ipbuf), "%hhu.%hhu.%hhu.%hhu", ip >> 24, ip >> 16, ip >> 8, ip); - if ((tok = AcquireToken(g_tok.b, ip, TB_CIDR)) < 64) { - if (tok > 8) { + if (!ipv6 && (tok = AcquireToken(g_tok.b, ip, TB_CIDR)) < 32) { + if (tok > 4) { LOG("%s rate limiting client\n", ipbuf, msg->version); Write(client.sock, "HTTP/1.1 429 Too Many Requests\r\n" "Content-Type: text/plain\r\n" "Connection: close\r\n" "\r\n" "429 Too Many Requests\n"); + } else { + Blackhole(ip); + ++g_banned; } ++g_ratelimits; break; @@ -1113,8 +1143,8 @@ void *HttpWorker(void *arg) { sent = write(client.sock, outbuf, p - outbuf); break; } else { - LOG("%s: 502 Claims Queue Full\n", ipbuf); - Write(client.sock, "HTTP/1.1 502 Claims Queue Full\r\n" + LOG("%s: 503 Claims Queue Full\n", ipbuf); + Write(client.sock, "HTTP/1.1 503 Claims Queue Full\r\n" "Content-Type: text/plain\r\n" "Connection: close\r\n" "\r\n" @@ -1793,6 +1823,15 @@ int main(int argc, char *argv[]) { CHECK_EQ(0, chdir("/opt/turfwar")); putenv("TMPDIR=/opt/turfwar/tmp"); + if ((g_blackhole.fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0)) == -1) { + kprintf("error: socket(AF_UNIX) failed: %s\n", strerror(errno)); + _Exit(3); + } + if (!Blackhole(0)) { + kprintf("redbean isn't able to protect your kernel from level 4 ddos\n"); + kprintf("please run the blackholed program, see https://justine.lol/\n"); + } + // the power to serve if (g_daemonize) { if (fork() > 0) _Exit(0); diff --git a/net/turfwar/turfwar.mk b/net/turfwar/turfwar.mk index 30aa0130b..b6e6a6e71 100644 --- a/net/turfwar/turfwar.mk +++ b/net/turfwar/turfwar.mk @@ -9,7 +9,8 @@ NET_TURFWAR_OBJS = \ $(NET_TURFWAR_SRCS:%.c=o/$(MODE)/%.o) NET_TURFWAR_COMS = \ - $(NET_TURFWAR_SRCS:%.c=o/$(MODE)/%.com) + $(NET_TURFWAR_SRCS:%.c=o/$(MODE)/%.com) \ + o/$(MODE)/net/turfwar/turfbean.com NET_TURFWAR_BINS = \ $(NET_TURFWAR_COMS) \ @@ -33,6 +34,7 @@ NET_TURFWAR_DIRECTDEPS = \ LIBC_X \ NET_HTTP \ THIRD_PARTY_GETOPT \ + THIRD_PARTY_MUSL \ THIRD_PARTY_NSYNC \ THIRD_PARTY_NSYNC_MEM \ THIRD_PARTY_SQLITE3 \ @@ -53,6 +55,30 @@ o/$(MODE)/net/turfwar/%.com.dbg: \ $(APE_NO_MODIFY_SELF) @$(APELINK) +o/$(MODE)/net/turfwar/turfbean.com.dbg: \ + $(TOOL_NET_DEPS) \ + o/$(MODE)/tool/net/redbean.o \ + $(TOOL_NET_REDBEAN_LUA_MODULES) \ + o/$(MODE)/tool/net/net.pkg \ + o/$(MODE)/net/turfwar/.init.lua.zip.o \ + o/$(MODE)/tool/net/redbean.png.zip.o \ + o/$(MODE)/tool/net/favicon.ico.zip.o \ + $(CRT) \ + $(APE_NO_MODIFY_SELF) + @$(APELINK) + +o/$(MODE)/net/turfwar/turfbean.com: \ + o/$(MODE)/net/turfwar/turfbean.com.dbg \ + o/$(MODE)/third_party/zip/zip.com \ + o/$(MODE)/tool/build/symtab.com + @$(MAKE_OBJCOPY) + @$(MAKE_SYMTAB_CREATE) + @$(MAKE_SYMTAB_ZIP) + +o/$(MODE)/net/turfwar/.init.lua.zip.o: private \ + ZIPOBJ_FLAGS += \ + -B + $(NET_TURFWAR_OBJS): \ $(BUILD_FILES) \ net/turfwar/turfwar.mk diff --git a/test/libc/calls/access_test.c b/test/libc/calls/access_test.c index be795f213..fe110c374 100644 --- a/test/libc/calls/access_test.c +++ b/test/libc/calls/access_test.c @@ -36,7 +36,7 @@ void SetUpOnce(void) { TEST(access, efault) { ASSERT_SYS(EFAULT, -1, access(0, F_OK)); - if (IsWindows() && !IsAsan()) return; // not possible + if (IsWindows() || !IsAsan()) return; // not possible ASSERT_SYS(EFAULT, -1, access((void *)77, F_OK)); } diff --git a/test/libc/calls/chdir_test.c b/test/libc/calls/chdir_test.c index 9fb7f5043..d02f73bec 100644 --- a/test/libc/calls/chdir_test.c +++ b/test/libc/calls/chdir_test.c @@ -30,7 +30,7 @@ void SetUpOnce(void) { TEST(chdir, efault) { ASSERT_SYS(EFAULT, -1, chdir(0)); - if (IsWindows() && !IsAsan()) return; // not possible + if (IsWindows() || !IsAsan()) return; // not possible ASSERT_SYS(EFAULT, -1, chdir((void *)77)); } diff --git a/test/libc/calls/lock2_test.c b/test/libc/calls/lock2_test.c new file mode 100644 index 000000000..a472513d3 --- /dev/null +++ b/test/libc/calls/lock2_test.c @@ -0,0 +1,168 @@ +/*-*- 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/calls.h" +#include "libc/calls/struct/flock.h" +#include "libc/calls/struct/sigaction.h" +#include "libc/calls/struct/sigset.h" +#include "libc/errno.h" +#include "libc/sysv/consts/at.h" +#include "libc/sysv/consts/f.h" +#include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/sig.h" +#include "libc/testlib/subprocess.h" +#include "libc/testlib/testlib.h" + +char testlib_enable_tmp_setup_teardown; + +TEST(lock, wholeFile) { + char buf[512]; + ASSERT_SYS(0, 3, open("db", O_RDWR | O_CREAT | O_EXCL, 0644)); + ASSERT_SYS(0, 0, fcntl(3, F_SETLK, &(struct flock){.l_type = F_RDLCK})); + ASSERT_SYS(0, 0, fcntl(3, F_SETLK, &(struct flock){.l_type = F_UNLCK})); + ASSERT_SYS(0, 0, close(3)); +} + +TEST(lock, testUpgradeFromReadToWriteLock) { + char buf[512]; + ASSERT_SYS(0, 3, open("db", O_RDWR | O_CREAT | O_EXCL, 0644)); + ASSERT_SYS(0, 0, + fcntl(3, F_SETLK, + &(struct flock){ + .l_type = F_RDLCK, + .l_start = 0x40000000, + .l_len = 1, + })); + ASSERT_SYS(0, 0, + fcntl(3, F_SETLK, + &(struct flock){ + .l_type = F_WRLCK, + .l_start = 0x40000000, + .l_len = 1, + })); + ASSERT_SYS(0, 0, close(3)); +} + +TEST(lock, testUpgradeWriteToWriteLock) { + char buf[512]; + ASSERT_SYS(0, 3, open("db", O_RDWR | O_CREAT | O_EXCL, 0644)); + ASSERT_SYS(0, 0, + fcntl(3, F_SETLK, + &(struct flock){ + .l_type = F_WRLCK, + .l_start = 0x40000000, + .l_len = 1, + })); + ASSERT_SYS(0, 0, + fcntl(3, F_SETLK, + &(struct flock){ + .l_type = F_WRLCK, + .l_start = 0x40000000, + .l_len = 1, + })); + ASSERT_SYS(0, 0, close(3)); +} + +TEST(lock, unlockEverything_unlocksSmallerRanges) { + int fd, pi[2]; + char buf[8] = {0}; + ASSERT_SYS(0, 3, creat("db", 0644)); + ASSERT_SYS(0, 0, close(3)); + ASSERT_SYS(0, 0, pipe(pi)); + SPAWN(fork); + ASSERT_SYS(0, 5, open("db", O_RDWR)); + ASSERT_SYS(0, 0, close(4)); + ASSERT_SYS(0, 8, read(3, buf, 8)); + ASSERT_SYS(0, 0, close(3)); + ASSERT_SYS(0, 0, + fcntl(5, F_SETLK, + &(struct flock){ + .l_type = F_WRLCK, + .l_start = 0x40000000, + .l_len = 1, + })); + ASSERT_SYS(0, 0, + fcntl(5, F_SETLK, + &(struct flock){ + .l_type = F_WRLCK, + .l_start = 0x40000005, + .l_len = 1, + })); + ASSERT_SYS(0, 0, close(5)); + PARENT(); + ASSERT_SYS(0, 0, close(3)); + ASSERT_NE(-1, (fd = open("db", O_RDWR))); + ASSERT_SYS(0, 0, + fcntl(fd, F_SETLK, + &(struct flock){ + .l_type = F_WRLCK, + .l_start = 0x40000000, + .l_len = 1, + })); + ASSERT_SYS(0, 0, + fcntl(fd, F_SETLK, + &(struct flock){ + .l_type = F_WRLCK, + .l_start = 0x40000005, + .l_len = 1, + })); + ASSERT_SYS(0, 0, + fcntl(fd, F_SETLK, + &(struct flock){ + .l_type = F_UNLCK, + })); + ASSERT_SYS(0, 8, write(4, buf, 8)); + ASSERT_SYS(0, 0, close(4)); + WAIT(exit, 0); + ASSERT_SYS(0, 0, close(fd)); +} + +TEST(lock, close_releasesLocks) { + int fd, pi[2]; + char buf[8] = {0}; + ASSERT_SYS(0, 3, creat("db", 0644)); + ASSERT_SYS(0, 0, close(3)); + ASSERT_SYS(0, 0, pipe(pi)); + SPAWN(fork); + ASSERT_SYS(0, 5, open("db", O_RDWR)); + ASSERT_SYS(0, 0, close(4)); + ASSERT_SYS(0, 8, read(3, buf, 8)); + ASSERT_SYS(0, 0, close(3)); + ASSERT_SYS(0, 0, + fcntl(5, F_SETLK, + &(struct flock){ + .l_type = F_WRLCK, + .l_start = 0x40000000, + .l_len = 1, + })); + ASSERT_SYS(0, 0, close(5)); + PARENT(); + ASSERT_SYS(0, 0, close(3)); + ASSERT_NE(-1, (fd = open("db", O_RDWR))); + ASSERT_SYS(0, 0, + fcntl(fd, F_SETLK, + &(struct flock){ + .l_type = F_WRLCK, + .l_start = 0x40000000, + .l_len = 1, + })); + ASSERT_SYS(0, 0, close(fd)); + ASSERT_SYS(0, 8, write(4, buf, 8)); + ASSERT_SYS(0, 0, close(4)); + WAIT(exit, 0); +} diff --git a/test/libc/calls/lock_ofd_test.c b/test/libc/calls/lock_ofd_test.c new file mode 100644 index 000000000..05eda094b --- /dev/null +++ b/test/libc/calls/lock_ofd_test.c @@ -0,0 +1,207 @@ +/*-*- 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/calls.h" +#include "libc/calls/struct/flock.h" +#include "libc/calls/syscall-sysv.internal.h" +#include "libc/dce.h" +#include "libc/errno.h" +#include "libc/intrin/kprintf.h" +#include "libc/macros.internal.h" +#include "libc/runtime/runtime.h" +#include "libc/sysv/consts/f.h" +#include "libc/sysv/consts/o.h" +#include "libc/testlib/testlib.h" +#include "libc/thread/thread.h" + +/** + * @fileoverview Better Advisory Locks Test + */ + +#define PROCESSES 8 +#define THREADS 8 // <-- THIS +#define RATIO 3 +#define ITERATIONS 10 + +char testlib_enable_tmp_setup_teardown; + +_Thread_local const char *kind; + +bool SupportsOfdLocks(void) { + int e; + bool r; + if (!IsLinux()) return false; + // F_OFD_* was introduced in linux 3.15 + // getrandom() was introduced in linux 3.17 + // testing for getrandom() should be a sure thing w/o creating an fd + e = errno; + r = !sys_getrandom(0, 0, 0); + errno = e; + return r; +} + +void SetUp(void) { + if (!SupportsOfdLocks()) { + kprintf("ofd locks not supported on this system\n"); + exit(0); + } +} + +void Log(const char *fmt, ...) { +#if 0 + va_list va; + char b[512]; + int i, n = sizeof(b); + va_start(va, fmt); + i = ksnprintf(b, n, "%s pid=%d tid=%d ", kind, getpid(), gettid()); + i += kvsnprintf(b + i, MAX(0, n - i), fmt, va); + i += ksnprintf(b + i, MAX(0, n - i), "\n"); + write(2, b, MIN(i, n)); + va_end(va); +#endif +} + +void Lock(int fd, int type, long start, long len) { + int e; + struct flock lock = { + .l_type = type, + .l_whence = SEEK_SET, + .l_start = start, + .l_len = len, + }; + e = errno; + while (fcntl(fd, F_OFD_SETLK, &lock)) { + ASSERT_TRUE(errno == EAGAIN || errno == EACCES); + errno = e; + Log("flock spinning on %d", fd); + } +} + +void WriteLock(int fd, long start, long len) { + Lock(fd, F_WRLCK, start, len); + Log("acquired write lock on %d", fd); +} + +void ReadLock(int fd, long start, long len) { + Lock(fd, F_RDLCK, start, len); + Log("acquired read lock on %d", fd); +} + +void Unlock(int fd, long start, long len) { + Lock(fd, F_UNLCK, start, len); + Log("released lock on %d", fd); +} + +void *Reader(void *arg) { + int i, j, fd; + char buf[10]; + kind = arg; + ASSERT_NE(-1, (fd = open("db", O_RDONLY))); + for (j = 0; j < ITERATIONS; ++j) { + ReadLock(fd, 10, 10); + for (i = 0; i < 10; ++i) { + ASSERT_SYS(0, 1, pread(fd, buf + i, 1, 10 + i)); + ASSERT_EQ(buf[0], buf[i]); + } + Unlock(fd, 10, 10); + sched_yield(); + } + ASSERT_SYS(0, 0, close(fd)); + return 0; +} + +void *Writer(void *arg) { + int i, j, fd; + char buf[10]; + kind = arg; + ASSERT_NE(-1, (fd = open("db", O_RDWR))); + for (j = 0; j < ITERATIONS; ++j) { + WriteLock(fd, 10, 10); + for (i = 0; i < 10; ++i) { + ASSERT_SYS(0, 1, pread(fd, buf + i, 1, 10 + i)); + ASSERT_EQ(buf[0], buf[i]); + } + for (i = 0; i < 10; ++i) { + buf[i]++; + } + for (i = 0; i < 10; ++i) { + ASSERT_SYS(0, 1, pwrite(fd, buf + i, 1, 10 + i)); + } + Unlock(fd, 10, 10); + sched_yield(); + } + ASSERT_SYS(0, 0, close(fd)); + return 0; +} + +TEST(posixAdvisoryLocks, threadsAndProcessesFightingForLock) { + int i, rc, pid, fd, ws; + pthread_t th[THREADS + 1]; + + ASSERT_SYS(0, 3, creat("db", 0644)); + ASSERT_SYS(0, 0, ftruncate(3, 30)); + ASSERT_SYS(0, 0, close(3)); + + for (i = 0; i < THREADS; ++i) { + if (i % RATIO == 0) { + ASSERT_EQ(0, pthread_create(th + i, 0, Reader, "reader thread")); + } else { + ASSERT_EQ(0, pthread_create(th + i, 0, Writer, "writer thread")); + } + } + + for (i = 0; i < PROCESSES; ++i) { + ASSERT_NE(-1, (rc = fork())); + if (!rc) { + if (i % RATIO == 0) { + Writer("writer process"); + } else { + Reader("reader process"); + } + _Exit(0); + } + } + + ASSERT_NE(-1, (fd = open("db", O_RDWR))); + Lock(fd, F_WRLCK, 0, 10); + Lock(fd, F_WRLCK, 20, 10); + + for (i = 0; i < THREADS; ++i) { + ASSERT_EQ(0, pthread_join(th[i], 0)); + } + + kind = "main process"; + for (;;) { + int e = errno; + if ((pid = waitpid(0, &ws, 0)) != -1) { + if (WIFSIGNALED(ws)) { + Log("process %d terminated with %G", pid, WTERMSIG(ws)); + testlib_incrementfailed(); + } else if (WEXITSTATUS(ws)) { + Log("process %d exited with %d", pid, WEXITSTATUS(ws)); + testlib_incrementfailed(); + } + } else { + ASSERT_EQ(ECHILD, errno); + errno = e; + break; + } + } + + ASSERT_SYS(0, 0, close(fd)); +} diff --git a/test/libc/calls/lock_test.c b/test/libc/calls/lock_test.c index c9f454a82..6b59c2f0e 100644 --- a/test/libc/calls/lock_test.c +++ b/test/libc/calls/lock_test.c @@ -33,7 +33,7 @@ */ #define PROCESSES 8 -#define THREADS (IsWindows() ? 8 : 0) +#define THREADS 0 // <-- consider F_OFD_* locks #define RATIO 3 #define ITERATIONS 10 diff --git a/test/libc/calls/open_test.c b/test/libc/calls/open_test.c index b04c54f3c..30e49a227 100644 --- a/test/libc/calls/open_test.c +++ b/test/libc/calls/open_test.c @@ -35,7 +35,7 @@ void SetUpOnce(void) { TEST(open, efault) { ASSERT_SYS(EFAULT, -1, open(0, O_RDONLY)); - if (IsWindows() && !IsAsan()) return; // not possible + if (IsWindows() || !IsAsan()) return; // not possible ASSERT_SYS(EFAULT, -1, open((void *)77, O_RDONLY)); } diff --git a/test/libc/calls/sigaction_test.c b/test/libc/calls/sigaction_test.c index b5d4f79db..6a78cc085 100644 --- a/test/libc/calls/sigaction_test.c +++ b/test/libc/calls/sigaction_test.c @@ -20,6 +20,7 @@ #include "libc/calls/struct/rusage.h" #include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/siginfo.h" +#include "libc/calls/struct/sigset.h" #include "libc/calls/ucontext.h" #include "libc/dce.h" #include "libc/errno.h" @@ -175,3 +176,27 @@ TEST(sigaction, ignoringSignalDiscardsSignal) { ASSERT_EQ(0, sigprocmask(SIG_UNBLOCK, &blocked, NULL)); EXPECT_EQ(0, OnSignalCnt); } + +TEST(sigaction, autoZombieSlayer) { + if (IsWindows()) return; + int pid; + struct sigaction sa; + // make sure we're starting in expected state + ASSERT_SYS(0, 0, sigaction(SIGCHLD, 0, &sa)); + ASSERT_EQ(SIG_DFL, sa.sa_handler); + // verify child becomes zombie + ASSERT_NE(-1, (pid = fork())); + if (!pid) _Exit(0); + ASSERT_SYS(0, pid, wait(0)); + // enable automatic zombie slayer + sa.sa_handler = SIG_IGN; + sa.sa_flags = SA_NOCLDWAIT; // seems to be optional + sigemptyset(&sa.sa_mask); + ASSERT_SYS(0, 0, sigaction(SIGCHLD, &sa, &sa)); + // verify it works + ASSERT_NE(-1, (pid = fork())); + if (!pid) _Exit(0); + ASSERT_SYS(ECHILD, -1, wait(0)); + // clean up + ASSERT_SYS(0, 0, sigaction(SIGCHLD, &sa, 0)); +} diff --git a/test/libc/calls/test.mk b/test/libc/calls/test.mk index 47cfc25f8..61ea7438d 100644 --- a/test/libc/calls/test.mk +++ b/test/libc/calls/test.mk @@ -117,6 +117,12 @@ o/$(MODE)/test/libc/calls/fcntl_test.com.runs: \ o/$(MODE)/test/libc/calls/lock_test.com.runs: \ private .PLEDGE = stdio rpath wpath cpath fattr proc flock +o/$(MODE)/test/libc/calls/lock2_test.com.runs: \ + private .PLEDGE = stdio rpath wpath cpath fattr proc flock + +o/$(MODE)/test/libc/calls/lock_ofd_test.com.runs: \ + private .PLEDGE = stdio rpath wpath cpath fattr proc flock + o/$(MODE)/test/libc/calls/openbsd_test.com.runs: \ private .PLEDGE = stdio rpath wpath cpath fattr proc unveil diff --git a/test/libc/calls/unlinkat_test.c b/test/libc/calls/unlinkat_test.c index d19facdaf..f194b3a05 100644 --- a/test/libc/calls/unlinkat_test.c +++ b/test/libc/calls/unlinkat_test.c @@ -30,7 +30,7 @@ void SetUpOnce(void) { TEST(unlink, efault) { ASSERT_SYS(EFAULT, -1, unlink(0)); - if (IsWindows() && !IsAsan()) return; // not possible + if (IsWindows() || !IsAsan()) return; // not possible ASSERT_SYS(EFAULT, -1, unlink((void *)77)); } diff --git a/test/libc/intrin/describesigset_test.c b/test/libc/intrin/describesigset_test.c index 47f1e377c..66c35a302 100644 --- a/test/libc/intrin/describesigset_test.c +++ b/test/libc/intrin/describesigset_test.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/struct/sigset.h" #include "libc/calls/struct/sigset.internal.h" +#include "libc/dce.h" #include "libc/intrin/describeflags.internal.h" #include "libc/sysv/consts/sig.h" #include "libc/testlib/testlib.h" @@ -25,7 +26,7 @@ TEST(DescribeSigset, full) { sigset_t ss; sigfillset(&ss); - EXPECT_STREQ("~{}", DescribeSigset(0, &ss)); + EXPECT_STREQ("~{ABRT,KILL,STOP}", DescribeSigset(0, &ss)); } TEST(DescribeSigset, present) { @@ -41,5 +42,9 @@ TEST(DescribeSigset, absent) { sigfillset(&ss); sigdelset(&ss, SIGINT); sigdelset(&ss, SIGUSR1); - EXPECT_STREQ("~{INT,USR1}", DescribeSigset(0, &ss)); + if (IsBsd()) { + EXPECT_STREQ("~{INT,ABRT,KILL,STOP,USR1}", DescribeSigset(0, &ss)); + } else { + EXPECT_STREQ("~{INT,ABRT,KILL,USR1,STOP}", DescribeSigset(0, &ss)); + } } diff --git a/test/libc/intrin/lockipc_test.c b/test/libc/intrin/lockipc_test.c index 78402dc0d..daac676ff 100644 --- a/test/libc/intrin/lockipc_test.c +++ b/test/libc/intrin/lockipc_test.c @@ -19,7 +19,6 @@ #include "libc/calls/calls.h" #include "libc/errno.h" #include "libc/intrin/bits.h" -#include "libc/intrin/intrin.h" #include "libc/intrin/kprintf.h" #include "libc/macros.internal.h" #include "libc/runtime/runtime.h" diff --git a/test/libc/intrin/lockscale_test.c b/test/libc/intrin/lockscale_test.c index e082ad2d1..dbb465c81 100644 --- a/test/libc/intrin/lockscale_test.c +++ b/test/libc/intrin/lockscale_test.c @@ -19,7 +19,6 @@ #include "libc/atomic.h" #include "libc/calls/calls.h" #include "libc/calls/struct/timespec.h" -#include "libc/intrin/intrin.h" #include "libc/mem/gc.h" #include "libc/mem/mem.h" #include "libc/stdio/stdio.h" diff --git a/test/libc/intrin/pthread_atfork_test.c b/test/libc/intrin/pthread_atfork_test.c index d15292c5e..0d7da5e2e 100644 --- a/test/libc/intrin/pthread_atfork_test.c +++ b/test/libc/intrin/pthread_atfork_test.c @@ -18,7 +18,6 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/dce.h" -#include "libc/intrin/intrin.h" #include "libc/mem/gc.h" #include "libc/mem/mem.h" #include "libc/runtime/internal.h" diff --git a/test/libc/intrin/putenv_test.c b/test/libc/intrin/putenv_test.c new file mode 100644 index 000000000..f2a7c7d34 --- /dev/null +++ b/test/libc/intrin/putenv_test.c @@ -0,0 +1,69 @@ +/*-*- 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 Gavin Arthur Hayes │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/mem/mem.h" +#include "libc/runtime/runtime.h" +#include "libc/str/str.h" +#include "libc/testlib/testlib.h" + +TEST(putenv, test) { + EXPECT_EQ(0, clearenv()); + EXPECT_EQ(0, putenv("hi=there")); + EXPECT_STREQ("there", getenv("hi")); + EXPECT_EQ(0, clearenv()); + EXPECT_EQ(0, putenv("hi=theretwo")); + EXPECT_STREQ("theretwo", getenv("hi")); + EXPECT_EQ(0, clearenv()); + EXPECT_EQ(0, setenv("hi", "therethree", 0)); + EXPECT_STREQ("therethree", getenv("hi")); +} + +TEST(putenv, usesProvidedMemory) { + char kv[32] = "hi=hello"; + EXPECT_EQ(0, putenv(kv)); + EXPECT_STREQ("hello", getenv("hi")); + strcpy(kv, "hi=there"); + EXPECT_STREQ("there", getenv("hi")); + EXPECT_EQ(0, unsetenv("hi")); + EXPECT_EQ(0, unsetenv("hi")); + EXPECT_EQ(0, getenv("hi")); + EXPECT_EQ(0, clearenv()); +} + +TEST(putenv, keyonly) { + EXPECT_EQ(0, clearenv()); + EXPECT_EQ(0, putenv("hi")); + EXPECT_STREQ("", getenv("hi")); + EXPECT_STREQ("hi", environ[0]); + EXPECT_EQ(0, environ[1]); + EXPECT_EQ(0, unsetenv("hi")); + EXPECT_EQ(0, getenv("hi")); + EXPECT_EQ(0, environ[0]); + EXPECT_EQ(0, environ[1]); +} + +TEST(putenv, environ) { + char *s = strdup("hi=there"); + EXPECT_EQ(0, clearenv()); + EXPECT_EQ(0, putenv(s)); + EXPECT_EQ(0, putenv(s)); + EXPECT_EQ(s, environ[0]); + EXPECT_EQ(0, environ[1]); + EXPECT_EQ(0, clearenv()); + free(s); +} diff --git a/test/libc/log/backtrace_test.c b/test/libc/log/backtrace_test.c index d2a3de7a3..aa46bfe62 100644 --- a/test/libc/log/backtrace_test.c +++ b/test/libc/log/backtrace_test.c @@ -22,6 +22,7 @@ #include "libc/errno.h" #include "libc/fmt/conv.h" #include "libc/intrin/asan.internal.h" +#include "libc/intrin/kprintf.h" #include "libc/limits.h" #include "libc/log/libfatal.internal.h" #include "libc/log/log.h" diff --git a/test/libc/mem/sortedints_test.c b/test/libc/mem/sortedints_test.c new file mode 100644 index 000000000..f77f89543 --- /dev/null +++ b/test/libc/mem/sortedints_test.c @@ -0,0 +1,126 @@ +/*-*- 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/limits.h" +#include "libc/mem/mem.h" +#include "libc/mem/sortedints.internal.h" +#include "libc/stdio/rand.h" +#include "libc/str/str.h" +#include "libc/testlib/ezbench.h" +#include "libc/testlib/testlib.h" + +struct SortedInts T; + +void TearDown(void) { + free(T.p); + bzero(&T, sizeof(T)); +} + +TEST(sortedints, test) { + EXPECT_TRUE(InsertInt(&T, 3, false)); + EXPECT_TRUE(InsertInt(&T, 1, false)); + EXPECT_TRUE(InsertInt(&T, -1, false)); + EXPECT_TRUE(InsertInt(&T, 2, false)); + EXPECT_EQ(4, T.n); + EXPECT_EQ(-1, T.p[0]); + EXPECT_EQ(+1, T.p[1]); + EXPECT_EQ(+2, T.p[2]); + EXPECT_EQ(+3, T.p[3]); + EXPECT_FALSE(ContainsInt(&T, -2)); + EXPECT_TRUE(ContainsInt(&T, -1)); + EXPECT_FALSE(ContainsInt(&T, 0)); + EXPECT_TRUE(ContainsInt(&T, 1)); + EXPECT_TRUE(ContainsInt(&T, 2)); + EXPECT_TRUE(ContainsInt(&T, 3)); + EXPECT_FALSE(ContainsInt(&T, 4)); +} + +TEST(sortedints, unique) { + EXPECT_TRUE(InsertInt(&T, INT_MAX, true)); + EXPECT_TRUE(InsertInt(&T, 1, true)); + EXPECT_FALSE(InsertInt(&T, INT_MAX, true)); + EXPECT_TRUE(InsertInt(&T, INT_MIN, true)); + EXPECT_FALSE(InsertInt(&T, 1, true)); + EXPECT_TRUE(InsertInt(&T, 2, true)); + EXPECT_EQ(4, T.n); + EXPECT_EQ(INT_MIN, T.p[0]); + EXPECT_EQ(+1, T.p[1]); + EXPECT_EQ(+2, T.p[2]); + EXPECT_EQ(INT_MAX, T.p[3]); + EXPECT_FALSE(ContainsInt(&T, -2)); + EXPECT_TRUE(ContainsInt(&T, INT_MIN)); + EXPECT_FALSE(ContainsInt(&T, 0)); + EXPECT_TRUE(ContainsInt(&T, 1)); + EXPECT_TRUE(ContainsInt(&T, 2)); + EXPECT_TRUE(ContainsInt(&T, INT_MAX)); + EXPECT_FALSE(ContainsInt(&T, 4)); + EXPECT_EQ(1, CountInt(&T, 1)); + EXPECT_EQ(0, CountInt(&T, -5)); +} + +TEST(sortedints, bag) { + EXPECT_TRUE(InsertInt(&T, INT_MAX, false)); + EXPECT_TRUE(InsertInt(&T, 1, false)); + EXPECT_TRUE(InsertInt(&T, INT_MAX, false)); + EXPECT_TRUE(InsertInt(&T, INT_MIN, false)); + EXPECT_TRUE(InsertInt(&T, 1, false)); + EXPECT_TRUE(InsertInt(&T, 2, false)); + EXPECT_EQ(6, T.n); + EXPECT_EQ(INT_MIN, T.p[0]); + EXPECT_EQ(1, T.p[1]); + EXPECT_EQ(1, T.p[2]); + EXPECT_EQ(2, T.p[3]); + EXPECT_EQ(INT_MAX, T.p[4]); + EXPECT_EQ(INT_MAX, T.p[5]); + EXPECT_FALSE(ContainsInt(&T, -2)); + EXPECT_TRUE(ContainsInt(&T, INT_MIN)); + EXPECT_FALSE(ContainsInt(&T, 0)); + EXPECT_TRUE(ContainsInt(&T, 1)); + EXPECT_TRUE(ContainsInt(&T, 2)); + EXPECT_TRUE(ContainsInt(&T, INT_MAX)); + EXPECT_FALSE(ContainsInt(&T, 4)); + EXPECT_EQ(1, CountInt(&T, INT_MIN)); + EXPECT_EQ(2, CountInt(&T, 1)); + EXPECT_EQ(0, CountInt(&T, -5)); +} + +TEST(sortedints, fuzz) { + for (int i = 0; i < 10000; ++i) { + volatile bool b; + volatile int y, x = lemur64(); + InsertInt(&T, x, x & 1); + b = ContainsInt(&T, x); + b = ContainsInt(&T, -x); + y = CountInt(&T, x); + } + for (int i = 1; i < T.n; ++i) { + ASSERT_GE(T.p[i], T.p[i - 1]); + } +} + +BENCH(sortedints, bench) { + volatile int x; + EZBENCH2("overhead", donothing, x = lemur64()); + EZBENCH2("insert small", donothing, InsertInt(&T, lemur64(), true)); + EZBENCH2("contains small", donothing, ContainsInt(&T, lemur64())); + for (int i = 0; i < 20000; ++i) { + InsertInt(&T, lemur64(), true); + } + EZBENCH2("insert big", donothing, InsertInt(&T, lemur64(), true)); + EZBENCH2("contains big", donothing, ContainsInt(&T, lemur64())); +} diff --git a/test/libc/stdio/dirstream_test.c b/test/libc/stdio/dirstream_test.c index bc0dfca18..5b6afc501 100644 --- a/test/libc/stdio/dirstream_test.c +++ b/test/libc/stdio/dirstream_test.c @@ -61,6 +61,8 @@ TEST(opendir, enotdir) { TEST(opendir, zipTest_fake) { ASSERT_NE(NULL, (dir = opendir("/zip"))); EXPECT_NE(NULL, (ent = readdir(dir))); + EXPECT_STREQ(".cosmo", ent->d_name); + EXPECT_NE(NULL, (ent = readdir(dir))); EXPECT_STREQ("echo", ent->d_name); EXPECT_NE(NULL, (ent = readdir(dir))); EXPECT_STREQ("usr", ent->d_name); @@ -68,6 +70,8 @@ TEST(opendir, zipTest_fake) { EXPECT_EQ(0, closedir(dir)); ASSERT_NE(NULL, (dir = opendir("/zip/"))); EXPECT_NE(NULL, (ent = readdir(dir))); + EXPECT_STREQ(".cosmo", ent->d_name); + EXPECT_NE(NULL, (ent = readdir(dir))); EXPECT_STREQ("echo", ent->d_name); EXPECT_NE(NULL, (ent = readdir(dir))); EXPECT_STREQ("usr", ent->d_name); diff --git a/test/libc/thread/pthread_cond_signal_test.c b/test/libc/thread/pthread_cond_signal_test.c index f72e77abd..c2aca7b4b 100644 --- a/test/libc/thread/pthread_cond_signal_test.c +++ b/test/libc/thread/pthread_cond_signal_test.c @@ -22,6 +22,8 @@ #include "libc/thread/thread.h" #include "libc/thread/thread2.h" +// TODO(jart): Can we make this test go faster on NetBSD? + int pos; int count; int limit; @@ -77,7 +79,7 @@ long Get(struct timespec *abs_deadline) { return v; } -#define N 10000 +#define N 1000 void *Producer(void *arg) { for (int i = 0; i < N; i++) { diff --git a/test/libc/thread/pthread_rwlock_rdlock_test.c b/test/libc/thread/pthread_rwlock_rdlock_test.c index b2e259168..c29dbcfec 100644 --- a/test/libc/thread/pthread_rwlock_rdlock_test.c +++ b/test/libc/thread/pthread_rwlock_rdlock_test.c @@ -17,8 +17,8 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/atomic.h" -#include "libc/mem/mem.h" #include "libc/mem/gc.h" +#include "libc/mem/mem.h" #include "libc/testlib/testlib.h" #include "libc/thread/spawn.h" #include "libc/thread/thread.h" diff --git a/test/libc/time/iso8601_test.c b/test/libc/time/iso8601_test.c new file mode 100644 index 000000000..5c420fa8e --- /dev/null +++ b/test/libc/time/iso8601_test.c @@ -0,0 +1,51 @@ +/*-*- 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/testlib/ezbench.h" +#include "libc/testlib/testlib.h" +#include "libc/time/struct/tm.h" +#include "libc/time/time.h" + +TEST(iso8601, test) { + char p[20]; + struct tm tm; + int64_t t = 0x62820bcd; + gmtime_r(&t, &tm); + EXPECT_EQ(p + 19, iso8601(p, &tm)); + EXPECT_STREQ("2022-05-16T08:31:09", p); +} + +TEST(iso8601us, test) { + char p[27]; + struct tm tm; + int64_t t = 0x62820bcd; + gmtime_r(&t, &tm); + EXPECT_EQ(p + 26, iso8601us(p, &tm, 1234000)); + EXPECT_STREQ("2022-05-16T08:31:09.001234", p); +} + +BENCH(iso8601, bench) { + char p[27]; + struct tm tm; + int64_t t = 0x62820bcd; + gmtime_r(&t, &tm); + EZBENCH2("iso8601", donothing, iso8601(p, &tm)); + EZBENCH2("iso8601us", donothing, iso8601us(p, &tm, 123456)); + EZBENCH2("strftime", donothing, + strftime(p, sizeof(p), "%Y-%m-%dT%H:%M:%S", &tm)); +} diff --git a/test/tool/build/lib/isnocompressext_test.c b/test/net/http/isnocompressext_test.c similarity index 93% rename from test/tool/build/lib/isnocompressext_test.c rename to test/net/http/isnocompressext_test.c index 7c47741cc..22766c7a9 100644 --- a/test/tool/build/lib/isnocompressext_test.c +++ b/test/net/http/isnocompressext_test.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ 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 │ @@ -17,11 +17,12 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/testlib/testlib.h" -#include "tool/build/lib/isnocompressext.h" +#include "net/http/http.h" TEST(IsNoCompressExt, test) { EXPECT_TRUE(IsNoCompressExt("MP4", -1)); EXPECT_TRUE(IsNoCompressExt("mp4", -1)); EXPECT_FALSE(IsNoCompressExt("dog", -1)); EXPECT_TRUE(IsNoCompressExt("dog.mp4", -1)); + EXPECT_FALSE(IsNoCompressExt("dog.mp4mp4mp4", -1)); } diff --git a/test/tool/net/redbean_test.c b/test/tool/net/redbean_test.c index b5c8c4b97..aa68c07b4 100644 --- a/test/tool/net/redbean_test.c +++ b/test/tool/net/redbean_test.c @@ -41,7 +41,7 @@ #include "third_party/regex/regex.h" STATIC_YOINK("zip_uri_support"); -STATIC_YOINK("o/" MODE "/tool/net/redbean.com"); +STATIC_YOINK("o/" MODE "/test/tool/net/redbean-tester.com"); char testlib_enable_tmp_setup_teardown_once; int port; @@ -52,9 +52,9 @@ void SetUpOnce(void) { char buf[1024]; int fdin, fdout; ASSERT_NE(-1, mkdir("bin", 0755)); - ASSERT_NE(-1, - (fdin = open("/zip/o/" MODE "/tool/net/redbean.com", O_RDONLY))); - ASSERT_NE(-1, (fdout = creat("bin/redbean.com", 0755))); + ASSERT_NE(-1, (fdin = open("/zip/o/" MODE "/test/tool/net/redbean-tester.com", + O_RDONLY))); + ASSERT_NE(-1, (fdout = creat("bin/redbean-tester.com", 0755))); for (;;) { ASSERT_NE(-1, (n = read(fdin, buf, sizeof(buf)))); if (!n) break; @@ -93,6 +93,7 @@ char *SendHttpRequest(const char *s) { bool Matches(const char *regex, const char *str) { bool r; regex_t re; + printf("%s\n", str); EXPECT_EQ(REG_OK, regcomp(&re, regex, 0)); r = regexec(&re, str, 0, 0, 0) == REG_OK; regfree(&re); @@ -113,14 +114,12 @@ TEST(redbean, testOptions) { close(0); open("/dev/null", O_RDWR); close(1); - dup(0); - close(2); - open("log", O_CREAT | O_TRUNC | O_WRONLY | O_APPEND, 0644); close(pipefds[0]); dup2(pipefds[1], 1); sigprocmask(SIG_SETMASK, &savemask, NULL); - execv("bin/redbean.com", - (char *const[]){"bin/redbean.com", "-vvszp0", "-l127.0.0.1", 0}); + execv("bin/redbean-tester.com", + (char *const[]){"bin/redbean-tester.com", "-vvszXp0", "-l127.0.0.1", + __strace > 0 ? "--strace" : 0, 0}); _exit(127); } EXPECT_NE(-1, close(pipefds[1])); @@ -139,7 +138,6 @@ TEST(redbean, testOptions) { EXPECT_NE(-1, kill(pid, SIGTERM)); EXPECT_NE(-1, wait(0)); EXPECT_NE(-1, sigprocmask(SIG_SETMASK, &savemask, 0)); - if (g_testlib_failed) fputs(gc(xslurp("log", 0)), stderr); } TEST(redbean, testPipeline) { @@ -155,13 +153,12 @@ TEST(redbean, testPipeline) { setpgrp(); close(0); open("/dev/null", O_RDWR); - close(2); - open("log", O_CREAT | O_TRUNC | O_WRONLY | O_APPEND, 0644); close(pipefds[0]); dup2(pipefds[1], 1); sigprocmask(SIG_SETMASK, &savemask, NULL); - execv("bin/redbean.com", - (char *const[]){"bin/redbean.com", "-vvszp0", "-l127.0.0.1", 0}); + execv("bin/redbean-tester.com", + (char *const[]){"bin/redbean-tester.com", "-vvszXp0", "-l127.0.0.1", + __strace > 0 ? "--strace" : 0, 0}); _exit(127); } EXPECT_NE(-1, close(pipefds[1])); @@ -189,5 +186,85 @@ TEST(redbean, testPipeline) { EXPECT_NE(-1, kill(pid, SIGTERM)); EXPECT_NE(-1, wait(0)); EXPECT_NE(-1, sigprocmask(SIG_SETMASK, &savemask, 0)); - if (g_testlib_failed) fputs(gc(xslurp("log", 0)), stderr); +} + +TEST(redbean, testContentRange) { + if (IsWindows()) return; + char portbuf[16]; + int pid, pipefds[2]; + sigset_t chldmask, savemask; + sigaddset(&chldmask, SIGCHLD); + EXPECT_NE(-1, sigprocmask(SIG_BLOCK, &chldmask, &savemask)); + ASSERT_NE(-1, pipe(pipefds)); + ASSERT_NE(-1, (pid = fork())); + if (!pid) { + setpgrp(); + close(0); + open("/dev/null", O_RDWR); + close(pipefds[0]); + dup2(pipefds[1], 1); + sigprocmask(SIG_SETMASK, &savemask, NULL); + execv("bin/redbean-tester.com", + (char *const[]){"bin/redbean-tester.com", "-vvszXp0", "-l127.0.0.1", + __strace > 0 ? "--strace" : 0, 0}); + _exit(127); + } + EXPECT_NE(-1, close(pipefds[1])); + EXPECT_NE(-1, read(pipefds[0], portbuf, sizeof(portbuf))); + port = atoi(portbuf); + + EXPECT_TRUE(Matches("\ +HTTP/1.1 206 Partial Content\r\n\ +Content-Range: bytes 18-21/52\r\n\ +Content-Type: text/plain; charset=utf-8\r\n\ +Vary: Accept-Encoding\r\n\ +Last-Modified: .*\r\n\ +Accept-Ranges: bytes\r\n\ +X-Content-Type-Options: nosniff\r\n\ +Date: .*\r\n\ +Server: redbean/.*\r\n\ +Content-Length: 4\r\n\ +\r\n\ +J\n\ +K\n", + gc(SendHttpRequest("GET /seekable.txt HTTP/1.1\r\n" + "Range: bytes=18-21\r\n" + "\r\n")))); + + EXPECT_TRUE(Matches("\ +HTTP/1.1 416 Range Not Satisfiable\r\n\ +Content-Range: bytes \\*/52\r\n\ +Content-Type: text/plain; charset=utf-8\r\n\ +Vary: Accept-Encoding\r\n\ +Last-Modified: .*\r\n\ +Accept-Ranges: bytes\r\n\ +X-Content-Type-Options: nosniff\r\n\ +Date: .*\r\n\ +Server: redbean/.*\r\n\ +Content-Length: 0\r\n\ +\r\n", + gc(SendHttpRequest("GET /seekable.txt HTTP/1.1\r\n" + "Range: bytes=-18-21\r\n" + "\r\n")))); + + EXPECT_TRUE(Matches("\ +HTTP/1.1 416 Range Not Satisfiable\r\n\ +Content-Range: bytes \\*/52\r\n\ +Content-Type: text/plain; charset=utf-8\r\n\ +Vary: Accept-Encoding\r\n\ +Last-Modified: .*\r\n\ +Accept-Ranges: bytes\r\n\ +X-Content-Type-Options: nosniff\r\n\ +Date: .*\r\n\ +Server: redbean/.*\r\n\ +Content-Length: 0\r\n\ +\r\n", + gc(SendHttpRequest("GET /seekable.txt HTTP/1.1\r\n" + "Range: bytes=18-60\r\n" + "\r\n")))); + + EXPECT_EQ(0, close(pipefds[0])); + EXPECT_NE(-1, kill(pid, SIGTERM)); + EXPECT_NE(-1, wait(0)); + EXPECT_NE(-1, sigprocmask(SIG_SETMASK, &savemask, 0)); } diff --git a/test/tool/net/sqlite_test.c b/test/tool/net/sqlite_test.c index 70c5f7255..3a827532c 100644 --- a/test/tool/net/sqlite_test.c +++ b/test/tool/net/sqlite_test.c @@ -31,7 +31,6 @@ char testlib_enable_tmp_setup_teardown; void SetUpOnce(void) { - if (IsWindows()) exit(0); sqlite3_initialize(); } diff --git a/test/tool/net/test.mk b/test/tool/net/test.mk index 67c430b9a..c4ebc5cfe 100644 --- a/test/tool/net/test.mk +++ b/test/tool/net/test.mk @@ -14,7 +14,7 @@ TEST_TOOL_NET_COMS = $(TEST_TOOL_NET_SRCS:%.c=o/$(MODE)/%.com) TEST_TOOL_NET_OBJS = \ $(TEST_TOOL_NET_SRCS:%.c=o/$(MODE)/%.o) \ - o/$(MODE)/tool/net/redbean.com.zip.o + o/$(MODE)/test/tool/net/redbean-tester.com.zip.o TEST_TOOL_NET_BINS = \ $(TEST_TOOL_NET_COMS) \ @@ -71,6 +71,26 @@ o/$(MODE)/test/tool/net/%.com.dbg: \ $(APE_NO_MODIFY_SELF) @$(APELINK) +o/$(MODE)/test/tool/net/redbean-tester.com.dbg: \ + $(TOOL_NET_DEPS) \ + o/$(MODE)/tool/net/redbean.o \ + $(TOOL_NET_REDBEAN_LUA_MODULES) \ + o/$(MODE)/tool/net/demo/seekable.txt.zip.o \ + o/$(MODE)/tool/net/net.pkg \ + $(CRT) \ + $(APE_NO_MODIFY_SELF) + @$(APELINK) + +o/$(MODE)/test/tool/net/redbean-tester.com: \ + o/$(MODE)/test/tool/net/redbean-tester.com.dbg \ + o/$(MODE)/third_party/zip/zip.com \ + o/$(MODE)/tool/build/symtab.com \ + $(TOOL_NET_REDBEAN_STANDARD_ASSETS) + @$(MAKE_OBJCOPY) + @$(MAKE_SYMTAB_CREATE) + @$(MAKE_SYMTAB_ZIP) + @$(TOOL_NET_REDBEAN_STANDARD_ASSETS_ZIP) + o/$(MODE)/test/tool/net/redbean_test.com.runs: \ private .PLEDGE = stdio rpath wpath cpath fattr proc inet diff --git a/third_party/python/Python/sysmodule.c b/third_party/python/Python/sysmodule.c index 9a1355244..625139b99 100644 --- a/third_party/python/Python/sysmodule.c +++ b/third_party/python/Python/sysmodule.c @@ -6,10 +6,10 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/dce.h" +#include "libc/mem/gc.internal.h" #include "libc/mem/mem.h" #include "libc/nt/dll.h" #include "libc/nt/version.h" -#include "libc/mem/gc.internal.h" #include "libc/runtime/runtime.h" #include "libc/stdio/stdio.h" #include "libc/str/locale.h" diff --git a/third_party/sqlite3/README.cosmo b/third_party/sqlite3/README.cosmo index 7f39d179e..df74597f3 100644 --- a/third_party/sqlite3/README.cosmo +++ b/third_party/sqlite3/README.cosmo @@ -13,4 +13,8 @@ LICENSE LOCAL CHANGES - - Changed the fsync() code to be configured at runtime. + - Added `/zip/.args` file support to SQLite shell + - Added `--strace` system call tracing flag to SQLite shell + - Added `--strace` function call logging flag to SQLite shell + - Configured fsync() using runtime magnums rather than ifdefs + - Save and restore errno in some places to avoid log pollution diff --git a/third_party/sqlite3/main.shell.c b/third_party/sqlite3/main.shell.c index 9c64591ad..681aa77a2 100644 --- a/third_party/sqlite3/main.shell.c +++ b/third_party/sqlite3/main.shell.c @@ -1 +1,2 @@ +STATIC_YOINK("zipos"); #include "third_party/sqlite3/main.c" diff --git a/third_party/sqlite3/shell.c b/third_party/sqlite3/shell.c index 0500f35c3..374009089 100644 --- a/third_party/sqlite3/shell.c +++ b/third_party/sqlite3/shell.c @@ -60,6 +60,9 @@ #include "libc/time/time.h" #include "libc/runtime/runtime.h" #include "libc/errno.h" +#include "libc/log/log.h" +#include "libc/runtime/symbols.internal.h" + #if SQLITE_USER_AUTHENTICATION #include "third_party/sqlite3/sqlite3userauth.inc" #endif @@ -10728,6 +10731,8 @@ static char *cmdline_option_value(int argc, char **argv, int i){ # endif #endif +STATIC_YOINK("zipos"); // for symtab + int SQLITE_CDECL main(int argc, char **argv){ char *zErrMsg = 0; ShellState data; @@ -10740,7 +10745,16 @@ int SQLITE_CDECL main(int argc, char **argv){ char **azCmd = 0; const char *zVfs = 0; /* Value of -vfs command-line option */ + // [jart] ensure %t symbols in strace log are symbolic + if (__strace > 0) { + GetSymbolTable(); + } + + // ShowCrashReports(); + + // [jart] support /zip/.args file for white labeling LoadZipArgs(&argc, &argv); + setBinaryMode(stdin, 0); setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */ setvbuf(stdin, (char *)NULL, _IONBF, BUFSIZ); diff --git a/third_party/sqlite3/sqlite3.mk b/third_party/sqlite3/sqlite3.mk index 4d596ca2a..a1c984505 100644 --- a/third_party/sqlite3/sqlite3.mk +++ b/third_party/sqlite3/sqlite3.mk @@ -57,6 +57,7 @@ THIRD_PARTY_SQLITE3_A_DIRECTDEPS = \ LIBC_THREAD \ LIBC_TIME \ LIBC_TINYMATH \ + LIBC_ZIPOS \ THIRD_PARTY_GDTOA \ THIRD_PARTY_LINENOISE \ THIRD_PARTY_MUSL \ diff --git a/third_party/zip/fileio.c b/third_party/zip/fileio.c index 371511eaf..e350503c1 100644 --- a/third_party/zip/fileio.c +++ b/third_party/zip/fileio.c @@ -1934,88 +1934,6 @@ int bfcopy(n) return ZE_OK; } - - -#ifdef NO_RENAME -int rename(from, to) -ZCONST char *from; -ZCONST char *to; -{ - unlink(to); - if (link(from, to) == -1) - return -1; - if (unlink(from) == -1) - return -1; - return 0; -} - -#endif /* NO_RENAME */ - - -#ifdef ZMEM - -/************************/ -/* Function memset() */ -/************************/ - -/* - * memset - for systems without it - * bill davidsen - March 1990 - */ - -char * -memset(buf, init, len) -register char *buf; /* buffer loc */ -register int init; /* initializer */ -register unsigned int len; /* length of the buffer */ -{ - char *start; - - start = buf; - while (len--) *(buf++) = init; - return(start); -} - - -/************************/ -/* Function memcpy() */ -/************************/ - -char * -memcpy(dst,src,len) /* v2.0f */ -register char *dst, *src; -register unsigned int len; -{ - char *start; - - start = dst; - while (len--) - *dst++ = *src++; - return(start); -} - - -/************************/ -/* Function memcmp() */ -/************************/ - -int -memcmp(b1,b2,len) /* jpd@usl.edu -- 11/16/90 */ -register char *b1, *b2; -register unsigned int len; -{ - - if (len) do { /* examine each byte (if any) */ - if (*b1++ != *b2++) - return (*((uch *)b1-1) - *((uch *)b2-1)); /* exit when miscompare */ - } while (--len); - - return(0); /* no miscompares, yield 0 result */ -} - -#endif /* ZMEM */ - - /*------------------------------------------------------------------ * Split archives */ diff --git a/tool/build/lib/buildlib.mk b/tool/build/lib/buildlib.mk index 4cdf39fe7..edf860840 100644 --- a/tool/build/lib/buildlib.mk +++ b/tool/build/lib/buildlib.mk @@ -49,6 +49,7 @@ TOOL_BUILD_LIB_A_DIRECTDEPS = \ LIBC_TIME \ LIBC_TINYMATH \ LIBC_X \ + NET_HTTP \ NET_HTTPS \ THIRD_PARTY_COMPILER_RT \ THIRD_PARTY_MBEDTLS \ diff --git a/tool/build/lib/elfwriter_zip.c b/tool/build/lib/elfwriter_zip.c index d86c4ac62..ee9ae1d97 100644 --- a/tool/build/lib/elfwriter_zip.c +++ b/tool/build/lib/elfwriter_zip.c @@ -20,9 +20,9 @@ #include "libc/fmt/conv.h" #include "libc/limits.h" #include "libc/log/check.h" +#include "libc/mem/gc.h" #include "libc/nexgen32e/crc32.h" #include "libc/nt/enum/fileflagandattributes.h" -#include "libc/mem/gc.h" #include "libc/stdio/rand.h" #include "libc/str/str.h" #include "libc/sysv/consts/s.h" @@ -30,9 +30,9 @@ #include "libc/x/x.h" #include "libc/x/xasprintf.h" #include "libc/zip.h" +#include "net/http/http.h" #include "third_party/zlib/zlib.h" #include "tool/build/lib/elfwriter.h" -#include "tool/build/lib/isnocompressext.h" #define ZIP_LOCALFILE_SECTION ".zip.2." #define ZIP_DIRECTORY_SECTION ".zip.4." diff --git a/tool/build/lib/isnocompressext.h b/tool/build/lib/isnocompressext.h deleted file mode 100644 index be7bb4b7f..000000000 --- a/tool/build/lib/isnocompressext.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef COSMOPOLITAN_TOOL_BUILD_LIB_ISNOCOMPRESSEXT_H_ -#define COSMOPOLITAN_TOOL_BUILD_LIB_ISNOCOMPRESSEXT_H_ -#if !(__ASSEMBLER__ + __LINKER__ + 0) -COSMOPOLITAN_C_START_ - -bool IsNoCompressExt(const char *, size_t); - -COSMOPOLITAN_C_END_ -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ -#endif /* COSMOPOLITAN_TOOL_BUILD_LIB_ISNOCOMPRESSEXT_H_ */ diff --git a/tool/net/counters.inc b/tool/net/counters.inc index 4f55e94f8..f0088857d 100644 --- a/tool/net/counters.inc +++ b/tool/net/counters.inc @@ -2,10 +2,12 @@ C(accepterrors) C(acceptflakes) C(acceptinterrupts) C(acceptresets) +C(accepts) C(badlengths) C(badmessages) C(badmethods) C(badranges) +C(bans) C(closeerrors) C(compressedresponses) C(connectionshandled) @@ -35,6 +37,7 @@ C(http11) C(http12) C(hugepayloads) C(identityresponses) +C(ignores) C(inflates) C(listingrequests) C(loops) @@ -57,6 +60,7 @@ C(readresets) C(readtimeouts) C(redirects) C(reindexes) +C(rejects) C(reloads) C(rewrites) C(serveroptions) diff --git a/tool/net/demo/.init.lua b/tool/net/demo/.init.lua index b6dbf1658..1f0c08855 100644 --- a/tool/net/demo/.init.lua +++ b/tool/net/demo/.init.lua @@ -1,6 +1,9 @@ mymodule = require "mymodule" sqlite3 = require "lsqlite3" +-- ddos protection +ProgramTokenBucket() + -- /.init.lua is loaded at startup in redbean's main process HidePath('/usr/share/zoneinfo/') HidePath('/usr/share/ssl/') diff --git a/tool/net/fetch.inc b/tool/net/fetch.inc index cff031720..64b5f55e8 100644 --- a/tool/net/fetch.inc +++ b/tool/net/fetch.inc @@ -26,8 +26,8 @@ static int LuaFetch(lua_State *L) { char *key, *val, *hdr; size_t keylen, vallen; size_t urlarglen, requestlen, paylen, bodylen; - size_t g, i, n, hdrsize; - int numredirects = 0, maxredirects = 5; + size_t g, n, hdrsize; + int imethod, numredirects = 0, maxredirects = 5; bool followredirect = true; struct addrinfo hints = {.ai_family = AF_INET, .ai_socktype = SOCK_STREAM, @@ -44,7 +44,12 @@ static int LuaFetch(lua_State *L) { body = luaL_optlstring(L, -1, "", &bodylen); lua_getfield(L, 2, "method"); // use GET by default if no method is provided - method = strtoupper(luaL_optstring(L, -1, kHttpMethod[kHttpGet])); + method = luaL_optstring(L, -1, kHttpMethod[kHttpGet]); + if ((imethod = GetHttpMethod(method, -1))) { + method = kHttpMethod[imethod]; + } else { + return LuaNilError(L, "bad method"); + } lua_getfield(L, 2, "followredirect"); if (lua_isboolean(L, -1)) followredirect = lua_toboolean(L, -1); lua_getfield(L, 2, "maxredirects"); diff --git a/tool/net/help.txt b/tool/net/help.txt index 8f2899fe3..d6e578096 100644 --- a/tool/net/help.txt +++ b/tool/net/help.txt @@ -1878,6 +1878,160 @@ FUNCTIONS string of unspecified format describing the error. Calls to this function may be wrapped in assert() if an exception is desired. + IsTrustedIp(ip:int) + └─→ bool + + Returns true if IP address is trustworthy. + + If the ProgramTrustedIp() function has NOT been called then redbean + will consider the networks 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, + and 192.168.0.0/16 to be trustworthy too. If ProgramTrustedIp() HAS + been called at some point earlier in your redbean's lifecycle, then + it'll trust the IPs and network subnets you specify instead. + + The network interface addresses used by the host machine are always + considered trustworthy, e.g. 127.0.0.1. This may change soon, if we + decide to export a GetHostIps() API which queries your NIC devices. + + ProgramTrustedIp(ip:int[, cidr:int]) + + Trusts an IP address or network. + + This function may be used to configure the IsTrustedIp() function + which is how redbean determines if a client is allowed to send us + headers like X-Forwarded-For (cf GetRemoteAddr vs. GetClientAddr) + without them being ignored. Trusted IPs is also how redbean turns + off token bucket rate limiting selectively, so be careful. Here's + an example of how you could trust all of Cloudflare's IPs: + + ProgramTrustedIp(ParseIp("103.21.244.0"), 22); + ProgramTrustedIp(ParseIp("103.22.200.0"), 22); + ProgramTrustedIp(ParseIp("103.31.4.0"), 22); + ProgramTrustedIp(ParseIp("104.16.0.0"), 13); + ProgramTrustedIp(ParseIp("104.24.0.0"), 14); + ProgramTrustedIp(ParseIp("108.162.192.0"), 18); + ProgramTrustedIp(ParseIp("131.0.72.0"), 22); + ProgramTrustedIp(ParseIp("141.101.64.0"), 18); + ProgramTrustedIp(ParseIp("162.158.0.0"), 15); + ProgramTrustedIp(ParseIp("172.64.0.0"), 13); + ProgramTrustedIp(ParseIp("173.245.48.0"), 20); + ProgramTrustedIp(ParseIp("188.114.96.0"), 20); + ProgramTrustedIp(ParseIp("190.93.240.0"), 20); + ProgramTrustedIp(ParseIp("197.234.240.0"), 22); + ProgramTrustedIp(ParseIp("198.41.128.0"), 17); + + Although you might want consider trusting redbean's open source + freedom embracing solution to DDOS protection instead! + + ProgramTokenBucket([replenish:num, cidr:int, reject:int, ignore:int, ban:int]) + + Enables DDOS protection. + + Imagine you have 2**32 buckets, one for each IP address. Each bucket + can hold about 127 tokens. Every second a background worker puts one + token in each bucket. When a TCP client socket is opened, it takes a + token from its bucket, and then proceeds. If the bucket holds only a + third of its original tokens, then redbean sends them a 429 warning. + If the client ignores this warning and keeps sending requests, until + there's no tokens left, then the banhammer finally comes down. + + This model of network rate limiting generously lets people "burst" a + tiny bit. For example someone might get a strong craving for content + and smash the reload button in Chrome 64 times in a fow seconds. But + since the client only get 1 new token per second, they'd better cool + their heels for a few minutes after doing that. This amount of burst + can be altered by choosing the `reject` / `ignore` / `ban` threshold + arguments. For example, if the `reject` parameter is set to 126 then + no bursting is allowed, which probably isn't a good idea. + + redbean is programmed to acquire a token immediately after accept() + is called from the main server process, which is well before fork() + or read() or any Lua code happens. redbean then takes action, based + on the token count, which can be accept / reject / ignore / ban. If + redbean determines a ban is warrented, then 4-byte datagram is sent + to the unix domain socket `/var/run/blackhole.sock` which should be + operated using the blackholed program we distribute separately. + + The trick redbean uses on Linux for example is insert rules in your + raw prerouting table. redbean is very fast at the application layer + so the biggest issue we've encountered in production is are kernels + themselves, and programming the raw prerouting table dynamically is + how we solved that. + + `replenish` is the number of times per second a token should be + added to each bucket. The default value is 1 which means one token + is granted per second to all buckets. The minimum value is 1/3600 + which means once per hour. The maximum value for this setting is + 1e6, which means once every microsecond. + + `cidr` is the specificity of judgement. Since creating 2^32 buckets + would need 4GB of RAM, redbean defaults this value to 24 which means + filtering applies to class c network blocks (i.e. x.x.x.*), and your + token buckets only take up 2^24 bytes of RAM (16MB). This can be set + to any number on the inclusive interval [8,32], where having a lower + number means you use less ram/cpu, but splash damage applies more to + your clients; whereas higher numbers means more ram/cpu usage, while + ensuring rate limiting only applies to specific compromised actors. + + `reject` is the token count or treshold at which redbean should send + 429 Too Many Request warnings to the client. Permitted values can be + anywhere between -1 and 126 inclusively. The default value is 30 and + -1 means disable to disable (assuming AcquireToken() will be used). + + `ignore` is the token count or treshold, at which redbean should try + simply ignoring clients and close the connection without logging any + kind of warning, and without sending any response. The default value + for this setting is `MIN(reject / 2, 15)`. This must be less than or + equal to the `reject` setting. Allowed values are [-1,126] where you + can use -1 as a means of disabling `ignore`. + + `ban` is the token count at which redbean should report IP addresses + to the blackhole daemon via a unix-domain socket datagram so they'll + get banned in the kernel routing tables. redbean's default value for + this setting is `MIN(ignore / 10, 1)`. Permitted values are [-1,126] + where -1 may be used as a means of disabling the `ban` feature. + + This function throws an exception if the constraints described above + are not the case. Warnings are logged should redbean fail to connect + to the blackhole daemon, assuming it hasn't been disabled. It's safe + to use load balancing tools when banning is enabled, since you can't + accidentally ban your own network interface addresses, loopback ips, + or ProgramTrustedIp() addresses where these rate limits don't apply. + + It's assumed will be called from the .init.lua global scope although + it could be used in interpreter mode, or from a forked child process + in which case the only processes that'll have ability to use it will + be that same process, and any descendent processes. This function is + only able to be called once. + + This feature is not available in unsecure mode. + + AcquireToken([ip:uint32]) + └─→ int8 + + Atomically acquires token. + + This routine atomically acquires a single token for an `ip` address. + The return value is the token count before the subtraction happened. + No action is taken based on the count, since the caller will decide. + + `ip` should be an IPv4 address and this defaults to GetClientAddr(), + although other interpretations of its meaning are possible. + + Your token buckets are stored in shared memory so this can be called + from multiple forked processes. which operate on the same values. + + CountTokens([ip:uint32]) + └─→ int8 + + Counts number of tokens in bucket. + + This function is the same as AcquireToken() except no subtraction is + performed, i.e. no token is taken. + + `ip` should be an IPv4 address and this defaults to GetClientAddr(), + although other interpretations of its meaning are possible. + ──────────────────────────────────────────────────────────────────────────────── CONSTANTS diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 2978cf151..1f5ca6b41 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" #include "libc/atomic.h" #include "libc/calls/calls.h" #include "libc/calls/ioctl.h" @@ -25,8 +26,10 @@ #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/rusage.h" #include "libc/calls/struct/sigaction.h" +#include "libc/calls/struct/sigset.h" #include "libc/calls/struct/stat.h" #include "libc/calls/struct/termios.h" +#include "libc/calls/struct/timespec.h" #include "libc/dce.h" #include "libc/dns/dns.h" #include "libc/dns/hoststxt.h" @@ -35,8 +38,8 @@ #include "libc/fmt/conv.h" #include "libc/fmt/itoa.h" #include "libc/intrin/atomic.h" +#include "libc/intrin/bits.h" #include "libc/intrin/bsr.h" -#include "libc/intrin/kprintf.h" #include "libc/intrin/likely.h" #include "libc/intrin/nomultics.internal.h" #include "libc/intrin/safemacros.internal.h" @@ -62,13 +65,16 @@ #include "libc/sock/goodsocket.internal.h" #include "libc/sock/sock.h" #include "libc/sock/struct/pollfd.h" +#include "libc/sock/struct/sockaddr.h" #include "libc/stdio/append.h" #include "libc/stdio/hex.internal.h" #include "libc/stdio/rand.h" #include "libc/stdio/stdio.h" #include "libc/str/slice.h" +#include "libc/str/str.h" #include "libc/str/strwidth.h" #include "libc/sysv/consts/af.h" +#include "libc/sysv/consts/clock.h" #include "libc/sysv/consts/clone.h" #include "libc/sysv/consts/dt.h" #include "libc/sysv/consts/ex.h" @@ -88,6 +94,7 @@ #include "libc/sysv/consts/sig.h" #include "libc/sysv/consts/sock.h" #include "libc/sysv/consts/termios.h" +#include "libc/sysv/consts/timer.h" #include "libc/sysv/consts/w.h" #include "libc/sysv/errfuns.h" #include "libc/thread/spawn.h" @@ -99,6 +106,7 @@ #include "net/http/escape.h" #include "net/http/http.h" #include "net/http/ip.h" +#include "net/http/tokenbucket.h" #include "net/http/url.h" #include "net/https/https.h" #include "third_party/getopt/getopt.h" @@ -334,13 +342,30 @@ static struct Assets { } * p; } assets; -static struct ProxyIps { +static struct TrustedIps { size_t n; - struct ProxyIp { + struct TrustedIp { uint32_t ip; uint32_t mask; } * p; -} proxyips; +} trustedips; + +struct TokenBucket { + signed char cidr; + signed char reject; + signed char ignore; + signed char ban; + struct timespec replenish; + union { + atomic_schar *b; + atomic_uint_fast64_t *w; + }; +} tokenbucket; + +struct Blackhole { + struct sockaddr_un addr; + int fd; +} blackhole; static struct Shared { int workers; @@ -467,6 +492,7 @@ static char gzip_footer[8]; static long maxpayloadsize; static const char *pidpath; static const char *logpath; +static uint32_t *interfaces; static const char *histpath; static struct pollfd *polls; static size_t payloadlength; @@ -858,19 +884,27 @@ static void ProgramRedirectArg(int code, const char *s) { ProgramRedirect(code, s, p - s, p + 1, n - (p - s + 1)); } -static void TrustProxy(uint32_t ip, int cidr) { +static void ProgramTrustedIp(uint32_t ip, int cidr) { uint32_t mask; mask = 0xffffffffu << (32 - cidr); - proxyips.p = xrealloc(proxyips.p, ++proxyips.n * sizeof(*proxyips.p)); - proxyips.p[proxyips.n - 1].ip = ip; - proxyips.p[proxyips.n - 1].mask = mask; + trustedips.p = xrealloc(trustedips.p, ++trustedips.n * sizeof(*trustedips.p)); + trustedips.p[trustedips.n - 1].ip = ip; + trustedips.p[trustedips.n - 1].mask = mask; } -static bool IsTrustedProxy(uint32_t ip) { +static bool IsTrustedIp(uint32_t ip) { int i; - if (proxyips.n) { - for (i = 0; i < proxyips.n; ++i) { - if ((ip & proxyips.p[i].mask) == proxyips.p[i].ip) { + uint32_t *p; + if (interfaces) { + for (p = interfaces; *p; ++p) { + if (ip == *p) { + return true; + } + } + } + if (trustedips.n) { + for (i = 0; i < trustedips.n; ++i) { + if ((ip & trustedips.p[i].mask) == trustedips.p[i].ip) { return true; } } @@ -909,7 +943,7 @@ static inline int GetRemoteAddr(uint32_t *ip, uint16_t *port) { char str[40]; GetClientAddr(ip, port); if (HasHeader(kHttpXForwardedFor)) { - if (IsTrustedProxy(*ip)) { + if (IsTrustedIp(*ip)) { if (ParseForwarded(HeaderData(kHttpXForwardedFor), HeaderLength(kHttpXForwardedFor), ip, port) == -1) { VERBOSEF("could not parse x-forwarded-for %`'.*s len=%ld", @@ -934,7 +968,7 @@ static char *DescribeClient(void) { uint32_t client; static char description[128]; GetClientAddr(&client, &port); - if (HasHeader(kHttpXForwardedFor) && IsTrustedProxy(client)) { + if (HasHeader(kHttpXForwardedFor) && IsTrustedIp(client)) { DescribeAddress(str, client, port); snprintf(description, sizeof(description), "%'.*s via %s", HeaderLength(kHttpXForwardedFor), HeaderData(kHttpXForwardedFor), @@ -1065,9 +1099,17 @@ static void ProgramHeader(const char *s) { } static void ProgramLogPath(const char *s) { + int fd; logpath = strdup(s); - close(2); - open(logpath, O_APPEND | O_WRONLY | O_CREAT, 0640); + fd = open(logpath, O_APPEND | O_WRONLY | O_CREAT, 0640); + if (fd == -1) { + WARNF("(srvr) open(%`'s) failed: %m", logpath); + return; + } + if (fd != 2) { + dup2(fd, 2); + close(fd); + } } static void ProgramPidPath(const char *s) { @@ -3394,7 +3436,7 @@ static int LuaRoute(lua_State *L) { return 1; } -static int LuaTrustProxy(lua_State *L) { +static int LuaProgramTrustedIp(lua_State *L) { lua_Integer ip, cidr; uint32_t ip32, imask; ip = luaL_checkinteger(L, 1); @@ -3415,18 +3457,18 @@ static int LuaTrustProxy(lua_State *L) { "it has bits masked by the cidr"); unreachable; } - TrustProxy(ip, cidr); + ProgramTrustedIp(ip, cidr); return 0; } -static int LuaIsTrustedProxy(lua_State *L) { +static int LuaIsTrusted(lua_State *L) { lua_Integer ip; ip = luaL_checkinteger(L, 1); if (!(0 <= ip && ip <= 0xffffffff)) { luaL_argerror(L, 1, "ip out of range"); unreachable; } - lua_pushboolean(L, IsTrustedProxy(ip)); + lua_pushboolean(L, IsTrustedIp(ip)); return 1; } @@ -4141,7 +4183,8 @@ static int LuaSetHeader(lua_State *L) { size_t i, keylen, vallen, evallen; OnlyCallDuringRequest(L, "SetHeader"); key = luaL_checklstring(L, 1, &keylen); - val = luaL_checklstring(L, 2, &vallen); + val = luaL_optlstring(L, 2, 0, &vallen); + if (!val) return 0; if ((h = GetHttpHeader(key, keylen)) == -1) { if (!IsValidHttpToken(key, keylen)) { luaL_argerror(L, 1, "invalid"); @@ -4161,11 +4204,7 @@ static int LuaSetHeader(lua_State *L) { } switch (h) { case kHttpConnection: - if (!SlicesEqualCase(eval, evallen, "close", 5)) { - luaL_argerror(L, 2, "unsupported"); - unreachable; - } - connectionclose = true; + connectionclose = SlicesEqualCase(eval, evallen, "close", 5); break; case kHttpContentType: p = AppendContentType(p, eval); @@ -4708,6 +4747,145 @@ static int LuaIsAssetCompressed(lua_State *L) { return 1; } +static void Blackhole(uint32_t ip) { + char buf[4]; + if (blackhole.fd <= 0) return; + WRITE32BE(buf, ip); + if (write(blackhole.fd, buf, 4) == -1) { + WARNF("error: write(%s) failed: %m\n", blackhole.addr.sun_path); + } +} + +wontreturn static void Replenisher(void) { + struct timespec ts; + VERBOSEF("token replenish worker started"); + signal(SIGINT, OnTerm); + signal(SIGHUP, OnTerm); + signal(SIGTERM, OnTerm); + signal(SIGUSR1, SIG_IGN); // make sure reload won't kill this + signal(SIGUSR2, SIG_IGN); // make sure meltdown won't kill this + ts = _timespec_real(); + while (!terminated) { + if (clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &ts, 0)) { + errno = 0; + continue; + } + ReplenishTokens(tokenbucket.w, (1ul << tokenbucket.cidr) / 8); + ts = _timespec_add(ts, tokenbucket.replenish); + } + VERBOSEF("token replenish worker exiting"); + _Exit(0); +} + +static int LuaAcquireToken(lua_State *L) { + uint32_t ip; + if (!tokenbucket.cidr) { + luaL_error(L, "ProgramTokenBucket() needs to be called first"); + unreachable; + } + GetClientAddr(&ip, 0); + lua_pushinteger(L, AcquireToken(tokenbucket.b, luaL_optinteger(L, 1, ip), + tokenbucket.cidr)); + return 1; +} + +static int LuaCountTokens(lua_State *L) { + uint32_t ip; + if (!tokenbucket.cidr) { + luaL_error(L, "ProgramTokenBucket() needs to be called first"); + unreachable; + } + GetClientAddr(&ip, 0); + lua_pushinteger(L, CountTokens(tokenbucket.b, luaL_optinteger(L, 1, ip), + tokenbucket.cidr)); + return 1; +} + +static int LuaProgramTokenBucket(lua_State *L) { + if (tokenbucket.cidr) { + luaL_error(L, "ProgramTokenBucket() can only be called once"); + unreachable; + } + lua_Number replenish = luaL_optnumber(L, 1, 1); // per second + lua_Integer cidr = luaL_optinteger(L, 2, 24); + lua_Integer reject = luaL_optinteger(L, 3, 30); + lua_Integer ignore = luaL_optinteger(L, 4, MIN(reject / 2, 15)); + lua_Integer ban = luaL_optinteger(L, 5, MIN(ignore / 10, 1)); + if (!(1 / 3600. <= replenish && replenish <= 1e6)) { + luaL_argerror(L, 1, "require 1/3600 <= replenish <= 1e6"); + unreachable; + } + if (!(8 <= cidr && cidr <= 32)) { + luaL_argerror(L, 2, "require 8 <= cidr <= 32"); + unreachable; + } + if (!(-1 <= reject && reject <= 126)) { + luaL_argerror(L, 3, "require -1 <= reject <= 126"); + unreachable; + } + if (!(-1 <= ignore && ignore <= 126)) { + luaL_argerror(L, 4, "require -1 <= ignore <= 126"); + unreachable; + } + if (!(-1 <= ban && ban <= 126)) { + luaL_argerror(L, 5, "require -1 <= ban <= 126"); + unreachable; + } + if (!(ignore <= reject)) { + luaL_argerror(L, 4, "require ignore <= reject"); + unreachable; + } + if (!(ban <= ignore)) { + luaL_argerror(L, 5, "require ban <= ignore"); + unreachable; + } + INFOF("deploying %,ld buckets " + "(one for every %ld ips) " + "each holding 127 tokens which " + "replenish %g times per second, " + "reject at %d tokens, " + "ignore at %d tokens, and " + "ban at %d tokens", + 1L << cidr, // + 4294967296 / (1L << cidr), // + replenish, // + reject, // + ignore, // + ban); + if (ignore == -1) ignore = -128; + if (ban == -1) ban = -128; + if (ban >= 0 && (IsLinux() || IsBsd())) { + struct Blackhole bh; + bh.addr.sun_family = AF_UNIX; + strlcpy(bh.addr.sun_path, "/var/run/blackhole.sock", + sizeof(bh.addr.sun_path)); + if ((bh.fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0)) == -1) { + WARNF("error: socket(AF_UNIX) failed: %m"); + ban = -1; + } else if (connect(bh.fd, (struct sockaddr *)&bh.addr, sizeof(bh.addr)) == + -1) { + WARNF("error: connect(%`'s) failed: %m", bh.addr.sun_path); + WARNF("redbean isn't able to protect your kernel from level 4 ddos"); + WARNF("please run the blackholed program, see https://justine.lol/"); + close(bh.fd); + ban = -1; + } else { + blackhole = bh; + } + } + tokenbucket.b = _mapshared(ROUNDUP(1ul << cidr, FRAMESIZE)); + memset(tokenbucket.b, 127, 1ul << cidr); + tokenbucket.cidr = cidr; + tokenbucket.reject = reject; + tokenbucket.ignore = ignore; + tokenbucket.ban = ban; + tokenbucket.replenish = _timespec_fromnanos(1 / replenish * 1e9); + int pid = fork(); + _npassert(pid != -1); + if (!pid) Replenisher(); + return 0; +} + static const char *GetContentTypeExt(const char *path, size_t n) { char *r, *e; int top; @@ -4961,7 +5139,7 @@ static const luaL_Reg kLuaFuncs[] = { {"IsPrivateIp", LuaIsPrivateIp}, // {"IsPublicIp", LuaIsPublicIp}, // {"IsReasonablePath", LuaIsReasonablePath}, // - {"IsTrustedProxy", LuaIsTrustedProxy}, // undocumented + {"IsTrustedIp", LuaIsTrusted}, // undocumented {"IsValidHttpToken", LuaIsValidHttpToken}, // {"LaunchBrowser", LuaLaunchBrowser}, // {"Lemur64", LuaLemur64}, // @@ -4992,6 +5170,7 @@ static const luaL_Reg kLuaFuncs[] = { {"ProgramPort", LuaProgramPort}, // {"ProgramRedirect", LuaProgramRedirect}, // {"ProgramTimeout", LuaProgramTimeout}, // + {"ProgramTrustedIp", LuaProgramTrustedIp}, // undocumented {"ProgramUid", LuaProgramUid}, // {"ProgramUniprocess", LuaProgramUniprocess}, // {"Rand64", LuaRand64}, // @@ -5020,7 +5199,6 @@ static const luaL_Reg kLuaFuncs[] = { {"Sleep", LuaSleep}, // {"Slurp", LuaSlurp}, // {"StoreAsset", LuaStoreAsset}, // - {"TrustProxy", LuaTrustProxy}, // undocumented {"Uncompress", LuaUncompress}, // {"Underlong", LuaUnderlong}, // {"VisualizeControlCodes", LuaVisualizeControlCodes}, // @@ -5029,6 +5207,8 @@ static const luaL_Reg kLuaFuncs[] = { {"hex", LuaHex}, // {"oct", LuaOct}, // #ifndef UNSECURE + {"AcquireToken", LuaAcquireToken}, // + {"CountTokens", LuaCountTokens}, // {"EvadeDragnetSurveillance", LuaEvadeDragnetSurveillance}, // {"GetSslIdentity", LuaGetSslIdentity}, // {"ProgramCertificate", LuaProgramCertificate}, // @@ -5040,6 +5220,7 @@ static const luaL_Reg kLuaFuncs[] = { {"ProgramSslPresharedKey", LuaProgramSslPresharedKey}, // {"ProgramSslRequired", LuaProgramSslRequired}, // {"ProgramSslTicketLifetime", LuaProgramSslTicketLifetime}, // + {"ProgramTokenBucket", LuaProgramTokenBucket}, // #endif // deprecated {"GetPayload", LuaGetBody}, // @@ -5267,6 +5448,8 @@ static void MemDestroy(void) { FreeStrings(&hidepaths); Free(&launchbrowser); Free(&serverheader); + Free(&trustedips.p); + Free(&interfaces); Free(&extrahdrs); Free(&pidpath); Free(&logpath); @@ -5368,6 +5551,16 @@ Content-Length: 0\r\n\ \r\n"); } +static ssize_t SendTooManyRequests(void) { + return SendString("\ +HTTP/1.1 429 Too Many Requests\r\n\ +Connection: close\r\n\ +Content-Type: text/plain\r\n\ +Content-Length: 22\r\n\ +\r\n\ +429 Too Many Requests\n"); +} + static void EnterMeltdownMode(void) { if (shared->lastmeltdown.tv_sec && !_timespec_gte(_timespec_sub(_timespec_real(), shared->lastmeltdown), @@ -5669,7 +5862,7 @@ static void ParseRequestParameters(void) { &url, kUrlPlus | kUrlLatin1)); if (!url.host.p) { if (HasHeader(kHttpXForwardedHost) && // - !GetRemoteAddr(&ip, 0) && IsTrustedProxy(ip)) { + !GetRemoteAddr(&ip, 0) && IsTrustedIp(ip)) { FreeLater(ParseHost(HeaderData(kHttpXForwardedHost), HeaderLength(kHttpXForwardedHost), &url)); } else if (HasHeader(kHttpHost)) { @@ -5925,12 +6118,17 @@ static char *ServeAsset(struct Asset *a, const char *path, size_t pathlen) { } else { return ServeError(500, "Internal Server Error"); } +#if 0 // TODO(jart): re-enable me } else if (!IsTiny() && cpm.msg.method != kHttpHead && !IsSslCompressed() && ClientAcceptsGzip() && + !(a->file && + IsNoCompressExt(a->file->path.s, a->file->path.n)) && ((cpm.contentlength >= 100 && _startswithi(ct, "text/")) || (cpm.contentlength >= 1000 && MeasureEntropy(cpm.content, 1000) < 7))) { + WARNF("serving compressed asset"); p = ServeAssetCompressed(a); +#endif } else { p = ServeAssetIdentity(a, ct); } @@ -6490,10 +6688,41 @@ static void MonitorMemory(void) { } static int HandleConnection(size_t i) { - int pid, rc = 0; + uint32_t ip; + int pid, tok, rc = 0; clientaddrsize = sizeof(clientaddr); if ((client = accept4(servers.p[i].fd, (struct sockaddr *)&clientaddr, &clientaddrsize, SOCK_CLOEXEC)) != -1) { + LockInc(&shared->c.accepts); + GetClientAddr(&ip, 0); + if (tokenbucket.cidr && tokenbucket.reject >= 0) { + if (!IsLoopbackIp(ip) && !IsTrustedIp(ip)) { + tok = AcquireToken(tokenbucket.b, ip, tokenbucket.cidr); + if (tok <= tokenbucket.ban && tokenbucket.ban >= 0) { + WARNF("(srvr) banning %hhu.%hhu.%hhu.%hhu who only has %d tokens", + ip >> 24, ip >> 16, ip >> 8, ip, tok); + LockInc(&shared->c.bans); + Blackhole(ip); + close(client); + return 0; + } else if (tok <= tokenbucket.ignore && tokenbucket.ignore >= 0) { + LockInc(&shared->c.ignores); + close(client); + return 0; + } else if (tok < tokenbucket.reject) { + WARNF("(srvr) rejecting %hhu.%hhu.%hhu.%hhu who only has %d tokens", + ip >> 24, ip >> 16, ip >> 8, ip, tok); + LockInc(&shared->c.rejects); + SendTooManyRequests(); + close(client); + return 0; + } + } else { + DEBUGF( + "(srvr) won't acquire token for whitelisted ip %hhu.%hhu.%hhu.%hhu", + ip >> 24, ip >> 16, ip >> 8, ip); + } + } startconnection = _timespec_real(); if (UNLIKELY(maxworkers) && shared->workers >= maxworkers) { EnterMeltdownMode(); @@ -6741,14 +6970,14 @@ static int HandlePoll(int ms) { static void Listen(void) { char ipbuf[16]; size_t i, j, n; - uint32_t ip, port, addrsize, *ifs, *ifp; + uint32_t ip, port, addrsize, *ifp; bool hasonserverlisten = IsHookDefined("OnServerListen"); if (!ports.n) { ProgramPort(8080); } if (!ips.n) { - if ((ifs = GetHostIps()) && *ifs) { - for (ifp = ifs; *ifp; ++ifp) { + if (interfaces && *interfaces) { + for (ifp = interfaces; *ifp; ++ifp) { sprintf(ipbuf, "%hhu.%hhu.%hhu.%hhu", *ifp >> 24, *ifp >> 16, *ifp >> 8, *ifp); ProgramAddr(ipbuf); @@ -6756,7 +6985,6 @@ static void Listen(void) { } else { ProgramAddr("0.0.0.0"); } - free(ifs); } servers.p = malloc(ips.n * ports.n * sizeof(*servers.p)); for (n = i = 0; i < ips.n; ++i) { @@ -7108,6 +7336,10 @@ void RedBean(int argc, char *argv[]) { SetDefaults(); LuaStart(); GetOpts(argc, argv); + // this can fail with EPERM if we're running under pledge() + if (!interpretermode && !(interfaces = GetHostIps())) { + WARNF("(srvr) failed to query system network interface addresses: %m"); + } #ifndef STATIC if (selfmodifiable) { MakeExecutableModifiable(); From 69bee64a5944a7e808be0dbf231a38e630af8588 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Wed, 19 Oct 2022 10:00:29 -0700 Subject: [PATCH 2/3] Make some last minute production changes --- net/turfwar/.init.lua | 30 ++++++++++++++++---- net/turfwar/blackhole.c | 14 +++------- net/turfwar/blackholed.c | 57 +++++++++++++++++++++++++------------- net/turfwar/blackholed.sh | 5 ---- net/turfwar/turfwar.c | 23 ++++++++++++--- test/libc/sock/unix_test.c | 56 +++++++++++++++++++++++++++++++++++++ tool/net/help.txt | 6 ++++ tool/net/redbean.c | 36 +++++++++++++----------- 8 files changed, 166 insertions(+), 61 deletions(-) delete mode 100755 net/turfwar/blackholed.sh diff --git a/net/turfwar/.init.lua b/net/turfwar/.init.lua index deef6b15a..fd037966f 100644 --- a/net/turfwar/.init.lua +++ b/net/turfwar/.init.lua @@ -1,31 +1,49 @@ -- reverse proxy for turfwar -ProgramPort(443) -ProgramTokenBucket() +if IsDaemon() then + ProgramPort(443) + ProgramUid(65534) + ProgramUid(65534) + ProgramLogPath('/var/log/turfbean.log') + ProgramPidPath('/var/log/turfbean.pid') + ProgramTrustedIp(ParseIp(Slurp('/etc/justine-ip.txt')), 32); + ProgramCertificate(Slurp('/etc/letsencrypt/live/ipv4.games-ecdsa/fullchain.pem')) + ProgramPrivateKey(Slurp('/etc/letsencrypt/live/ipv4.games-ecdsa/privkey.pem')) +end RELAY_HEADERS_TO_CLIENT = { 'Access-Control-Allow-Origin', 'Cache-Control', 'Connection', - 'Content-Encoding', 'Content-Type', 'Last-Modified', 'Referrer-Policy', 'Vary', } +function OnServerStart() + ProgramTokenBucket() + assert(unix.setrlimit(unix.RLIMIT_NPROC, 1000, 1000)) +end + function OnWorkerStart() + assert(unix.setrlimit(unix.RLIMIT_RSS, 2*1024*1024)) + assert(unix.setrlimit(unix.RLIMIT_CPU, 2)) assert(unix.unveil(nil, nil)) assert(unix.pledge("stdio inet", nil, unix.PLEDGE_PENALTY_RETURN_EPERM)) end function OnHttpRequest() + local url = 'http://127.0.0.1' .. EscapePath(GetPath()) + local name = GetParam('name') + if name then + url = url .. '?name=' .. EscapeParam(name) + end local status, headers, body = - Fetch('http://127.0.0.1' .. EscapePath(GetPath()), + Fetch(url, {method = GetMethod(), headers = { ['Accept'] = GetHeader('Accept'), - ['Accept-Encoding'] = GetHeader('Accept-Encoding'), ['CF-IPCountry'] = GetHeader('CF-IPCountry'), ['If-Modified-Since'] = GetHeader('If-Modified-Since'), ['Referer'] = GetHeader('Referer'), @@ -39,7 +57,7 @@ function OnHttpRequest() end Write(body) else - err = headers + local err = headers Log(kLogError, "proxy failed %s" % {err}) ServeError(503) end diff --git a/net/turfwar/blackhole.c b/net/turfwar/blackhole.c index 9f38ca0d1..a57eac330 100644 --- a/net/turfwar/blackhole.c +++ b/net/turfwar/blackhole.c @@ -37,18 +37,11 @@ int main(int argc, char *argv[]) { } int fd; - struct sockaddr_un addr = { - AF_UNIX, - "/var/run/blackhole.sock", - }; + struct sockaddr_un addr = {AF_UNIX, "/var/run/blackhole.sock"}; if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) { kprintf("error: socket(AF_UNIX) failed: %s\n", strerror(errno)); return 3; } - if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { - kprintf("error: connect(%#s) failed: %s\n", addr.sun_path, strerror(errno)); - return 4; - } int rc = 0; for (int i = 1; i < argc; ++i) { @@ -56,8 +49,9 @@ int main(int argc, char *argv[]) { char buf[4]; if ((ip = ParseIp(argv[i], -1)) != -1) { WRITE32BE(buf, ip); - if (write(fd, buf, 4) == -1) { - kprintf("error: write() failed: %s\n", strerror(errno)); + if (sendto(fd, buf, 4, 0, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + kprintf("error: sendto(%#s) failed: %s\n", addr.sun_path, + strerror(errno)); rc |= 2; } } else { diff --git a/net/turfwar/blackholed.c b/net/turfwar/blackholed.c index 7a3e09fda..c83f0202b 100644 --- a/net/turfwar/blackholed.c +++ b/net/turfwar/blackholed.c @@ -45,6 +45,7 @@ #include "libc/sysv/consts/sock.h" #include "libc/sysv/consts/timer.h" #include "libc/time/struct/tm.h" +#include "net/http/http.h" #include "third_party/getopt/getopt.h" #include "third_party/musl/passwd.h" @@ -55,12 +56,13 @@ #define DEFAULT_LOGNAME "/var/log/blackhole.log" #define DEFAULT_PIDNAME "/var/run/blackhole.pid" #define DEFAULT_SOCKNAME "/var/run/blackhole.sock" -#define GETOPTS "L:S:P:M:G:dh" +#define GETOPTS "L:S:P:M:G:W:dh" #define USAGE \ "\ -Usage: blackholed [-hdLPSMG]\n\ +Usage: blackholed [-hdLPSMGW]\n\ -h help\n\ -d daemonize\n\ + -W IP whitelist ip address\n\ -L PATH log file name (default: " DEFAULT_LOGNAME ")\n\ -P PATH pid file name (default: " DEFAULT_PIDNAME ")\n\ -S PATH socket file name (default: " DEFAULT_SOCKNAME ")\n\ @@ -131,6 +133,7 @@ const char *g_sockname; const char *g_iptables; sig_atomic_t g_shutdown; struct SortedInts g_blocked; +struct SortedInts g_whitelisted; static wontreturn void ShowUsage(int fd, int rc) { write(fd, USAGE, sizeof(USAGE) - 1); @@ -139,8 +142,23 @@ static wontreturn void ShowUsage(int fd, int rc) { _Exit(rc); } -static void GetOpts(int argc, char *argv[]) { +char *GetTimestamp(void) { + struct timespec ts; + static struct tm tm; + static int64_t last; + static char str[27]; + clock_gettime(0, &ts); + if (ts.tv_sec != last) { + localtime_r(&ts.tv_sec, &tm); + last = ts.tv_sec; + } + iso8601us(str, &tm, ts.tv_nsec); + return str; +} + +void GetOpts(int argc, char *argv[]) { int opt; + int64_t ip; g_sockmode = 0777; g_pidname = DEFAULT_PIDNAME; g_logname = DEFAULT_LOGNAME; @@ -165,6 +183,16 @@ static void GetOpts(int argc, char *argv[]) { case 'M': g_sockmode = strtol(optarg, 0, 8) & 0777; break; + case 'W': + if ((ip = ParseIp(optarg, -1)) != -1) { + if (InsertInt(&g_whitelisted, ip, true)) { + LOG("whitelisted %s", optarg); + } + } else { + kprintf("error: could not parse -W %#s IP address\n", optarg); + _Exit(1); + } + break; case 'h': ShowUsage(1, 0); default: @@ -173,20 +201,6 @@ static void GetOpts(int argc, char *argv[]) { } } -char *GetTimestamp(void) { - struct timespec ts; - static struct tm tm; - static int64_t last; - static char str[27]; - clock_gettime(0, &ts); - if (ts.tv_sec != last) { - localtime_r(&ts.tv_sec, &tm); - last = ts.tv_sec; - } - iso8601us(str, &tm, ts.tv_nsec); - return str; -} - void OnTerm(int sig) { char tmp[15]; LOG("got %s", strsignal_r(sig, tmp)); @@ -278,9 +292,11 @@ void Daemonize(void) { } void UseLog(void) { - _npassert(dup2(g_logfd, 2) == 2); - if (g_logfd != 2) { - _npassert(!close(g_logfd)); + if (g_logfd > 0) { + _npassert(dup2(g_logfd, 2) == 2); + if (g_logfd != 2) { + _npassert(!close(g_logfd)); + } } } @@ -426,6 +442,7 @@ int main(int argc, char *argv[]) { if ((ip = READ32BE(msg))) { if (IsMyIp(ip) || // nics + ContainsInt(&g_whitelisted, ip) || // protected (ip & 0xff000000) == 0x00000000 || // 0.0.0.0/8 (ip & 0xff000000) == 0x7f000000) { // 127.0.0.0/8 LOG("won't block %s", FormatIp(ip)); diff --git a/net/turfwar/blackholed.sh b/net/turfwar/blackholed.sh deleted file mode 100755 index 053defc1a..000000000 --- a/net/turfwar/blackholed.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -make -j16 o//net/turfwar/blackholed.elf && -sudo chown root o//net/turfwar/blackholed.elf && -sudo chmod 06755 o//net/turfwar/blackholed.elf && -exec o//net/turfwar/blackholed.elf diff --git a/net/turfwar/turfwar.c b/net/turfwar/turfwar.c index 43a71df8c..71ef1c908 100644 --- a/net/turfwar/turfwar.c +++ b/net/turfwar/turfwar.c @@ -39,6 +39,7 @@ #include "libc/macros.internal.h" #include "libc/mem/gc.h" #include "libc/mem/mem.h" +#include "libc/mem/sortedints.internal.h" #include "libc/nexgen32e/crc32.h" #include "libc/paths.h" #include "libc/runtime/internal.h" @@ -118,13 +119,14 @@ #define TB_BYTES (1u << TB_CIDR) #define TB_WORDS (TB_BYTES / 8) -#define GETOPTS "idvp:w:k:" +#define GETOPTS "idvp:w:k:W:" #define USAGE \ "\ Usage: turfwar.com [-dv] ARGS...\n\ -i integrity check and vacuum at startup\n\ -d daemonize\n\ -v verbosity\n\ + -W IP whitelist\n\ -p INT port\n\ -w INT workers\n\ -k INT keepalive\n\ @@ -247,6 +249,7 @@ bool g_daemonize; int g_port = PORT; int g_workers = WORKERS; int g_keepalive = KEEPALIVE_MS; +struct SortedInts g_whitelisted; // lifecycle vars pthread_t g_listener; @@ -417,7 +420,7 @@ bool Blackhole(uint32_t ip) { sizeof(g_blackhole.addr)) == 4) { return true; } else { - kprintf("error: sendto(/var/run/blackhole.sock) failed: %s\n", + kprintf("error: sendto(%#s) failed: %s\n", g_blackhole.addr.sun_path, strerror(errno)); return false; } @@ -891,7 +894,8 @@ void *HttpWorker(void *arg) { ksnprintf(ipbuf, sizeof(ipbuf), "%hhu.%hhu.%hhu.%hhu", ip >> 24, ip >> 16, ip >> 8, ip); - if (!ipv6 && (tok = AcquireToken(g_tok.b, ip, TB_CIDR)) < 32) { + if (!ipv6 && !ContainsInt(&g_whitelisted, ip) && + (tok = AcquireToken(g_tok.b, ip, TB_CIDR)) < 32) { if (tok > 4) { LOG("%s rate limiting client\n", ipbuf, msg->version); Write(client.sock, "HTTP/1.1 429 Too Many Requests\r\n" @@ -1342,8 +1346,9 @@ void OnCtrlC(int sig) { } // parses cli arguments -static void GetOpts(int argc, char *argv[]) { +void GetOpts(int argc, char *argv[]) { int opt; + int64_t ip; while ((opt = getopt(argc, argv, GETOPTS)) != -1) { switch (opt) { case 'i': @@ -1364,6 +1369,16 @@ static void GetOpts(int argc, char *argv[]) { case 'v': ++__log_level; break; + case 'W': + if ((ip = ParseIp(optarg, -1)) != -1) { + if (InsertInt(&g_whitelisted, ip, true)) { + LOG("whitelisted %s", optarg); + } + } else { + kprintf("error: could not parse -w %#s IP address\n", optarg); + _Exit(1); + } + break; case '?': write(1, USAGE, sizeof(USAGE) - 1); exit(0); diff --git a/test/libc/sock/unix_test.c b/test/libc/sock/unix_test.c index 3ac20bf97..2b34bed48 100644 --- a/test/libc/sock/unix_test.c +++ b/test/libc/sock/unix_test.c @@ -20,6 +20,7 @@ #include "libc/calls/internal.h" #include "libc/calls/struct/timeval.h" #include "libc/dce.h" +#include "libc/errno.h" #include "libc/nt/version.h" #include "libc/runtime/runtime.h" #include "libc/sock/sock.h" @@ -119,3 +120,58 @@ TEST(unix, stream) { EXPECT_EQ(0, WEXITSTATUS(ws)); alarm(0); } + +TEST(unix, serverGoesDown_deletedSockFile) { // field of landmine + if (IsWindows()) return; + int ws, rc; + char buf[8] = {0}; + uint32_t len = sizeof(struct sockaddr_un); + struct sockaddr_un addr = {AF_UNIX, "foo.sock"}; + ASSERT_SYS(0, 3, socket(AF_UNIX, SOCK_DGRAM, 0)); + ASSERT_SYS(0, 0, bind(3, (void *)&addr, len)); + ASSERT_SYS(0, 4, socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0)); + ASSERT_SYS(0, 0, connect(4, (void *)&addr, len)); + ASSERT_SYS(0, 5, write(4, "hello", 5)); + ASSERT_SYS(0, 5, read(3, buf, 8)); + ASSERT_SYS(0, 0, close(3)); + ASSERT_SYS(IsBsd() ? ECONNRESET : ECONNREFUSED, -1, write(4, "hello", 5)); + ASSERT_SYS(0, 0, unlink(addr.sun_path)); + ASSERT_SYS(0, 3, socket(AF_UNIX, SOCK_DGRAM, 0)); + ASSERT_SYS(0, 0, bind(3, (void *)&addr, len)); + rc = write(4, "hello", 5); + ASSERT_TRUE(rc == -1 && (errno == ECONNRESET || // + errno == ENOTCONN || // + errno == ECONNREFUSED || // + errno == EDESTADDRREQ)); + errno = 0; + ASSERT_SYS(0, 0, close(4)); + ASSERT_SYS(0, 4, socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0)); + ASSERT_SYS(0, 0, connect(4, (void *)&addr, len)); + ASSERT_SYS(0, 5, write(4, "hello", 5)); + ASSERT_SYS(0, 5, read(3, buf, 8)); + ASSERT_SYS(0, 0, close(4)); + ASSERT_SYS(0, 0, close(3)); +} + +TEST(unix, serverGoesDown_usingSendTo_unlink) { // much easier + if (IsWindows()) return; + int ws, rc; + char buf[8] = {0}; + uint32_t len = sizeof(struct sockaddr_un); + struct sockaddr_un addr = {AF_UNIX, "foo.sock"}; + ASSERT_SYS(0, 3, socket(AF_UNIX, SOCK_DGRAM, 0)); + ASSERT_SYS(0, 0, bind(3, (void *)&addr, len)); + ASSERT_SYS(0, 4, socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0)); + ASSERT_SYS(0, 5, sendto(4, "hello", 5, 0, (void *)&addr, len)); + ASSERT_SYS(0, 5, read(3, buf, 8)); + ASSERT_SYS(0, 0, close(3)); + ASSERT_SYS(ECONNREFUSED, -1, sendto(4, "hello", 5, 0, (void *)&addr, len)); + ASSERT_SYS(0, 0, unlink(addr.sun_path)); + ASSERT_SYS(ENOENT, -1, sendto(4, "hello", 5, 0, (void *)&addr, len)); + ASSERT_SYS(0, 3, socket(AF_UNIX, SOCK_DGRAM, 0)); + ASSERT_SYS(0, 0, bind(3, (void *)&addr, len)); + ASSERT_SYS(0, 5, sendto(4, "hello", 5, 0, (void *)&addr, len)); + ASSERT_SYS(0, 5, read(3, buf, 8)); + ASSERT_SYS(0, 0, close(4)); + ASSERT_SYS(0, 0, close(3)); +} diff --git a/tool/net/help.txt b/tool/net/help.txt index d6e578096..9ee031b39 100644 --- a/tool/net/help.txt +++ b/tool/net/help.txt @@ -1935,6 +1935,12 @@ FUNCTIONS If the client ignores this warning and keeps sending requests, until there's no tokens left, then the banhammer finally comes down. + function OnServerStart() + ProgramTokenBucket() + ProgramTrustedIp(ParseIp('x.x.x.x'), 32) + assert(unix.setrlimit(unix.RLIMIT_NPROC, 1000, 1000)) + end + This model of network rate limiting generously lets people "burst" a tiny bit. For example someone might get a strong craving for content and smash the reload button in Chrome 64 times in a fow seconds. But diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 1f5ca6b41..44712918b 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -2171,7 +2171,12 @@ static bool OpenZip(bool force) { } } } else { - WARNF("(zip) stat() error: %m"); + // avoid noise if we setuid to user who can't see executable + if (errno == EACCES) { + VERBOSEF("(zip) stat(%`'s) error: %m", zpath); + } else { + WARNF("(zip) stat(%`'s) error: %m", zpath); + } } return false; } @@ -4749,10 +4754,12 @@ static int LuaIsAssetCompressed(lua_State *L) { static void Blackhole(uint32_t ip) { char buf[4]; - if (blackhole.fd <= 0) return; + if (blackhole.fd > 0) return; WRITE32BE(buf, ip); - if (write(blackhole.fd, buf, 4) == -1) { - WARNF("error: write(%s) failed: %m\n", blackhole.addr.sun_path); + if (sendto(blackhole.fd, &buf, 4, 0, (struct sockaddr *)&blackhole.addr, + sizeof(blackhole.addr)) == -1) { + VERBOSEF("error: sendto(%s) failed: %m\n", blackhole.addr.sun_path); + errno = 0; } } @@ -4855,22 +4862,19 @@ static int LuaProgramTokenBucket(lua_State *L) { if (ignore == -1) ignore = -128; if (ban == -1) ban = -128; if (ban >= 0 && (IsLinux() || IsBsd())) { - struct Blackhole bh; - bh.addr.sun_family = AF_UNIX; - strlcpy(bh.addr.sun_path, "/var/run/blackhole.sock", - sizeof(bh.addr.sun_path)); - if ((bh.fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0)) == -1) { + uint32_t testip = 0; + blackhole.addr.sun_family = AF_UNIX; + strlcpy(blackhole.addr.sun_path, "/var/run/blackhole.sock", + sizeof(blackhole.addr.sun_path)); + if ((blackhole.fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0)) == -1) { WARNF("error: socket(AF_UNIX) failed: %m"); ban = -1; - } else if (connect(bh.fd, (struct sockaddr *)&bh.addr, sizeof(bh.addr)) == - -1) { - WARNF("error: connect(%`'s) failed: %m", bh.addr.sun_path); + } else if (sendto(blackhole.fd, &testip, 4, 0, + (struct sockaddr *)&blackhole.addr, + sizeof(blackhole.addr)) == -1) { + WARNF("error: sendto(%`'s) failed: %m", blackhole.addr.sun_path); WARNF("redbean isn't able to protect your kernel from level 4 ddos"); WARNF("please run the blackholed program, see https://justine.lol/"); - close(bh.fd); - ban = -1; - } else { - blackhole = bh; } } tokenbucket.b = _mapshared(ROUNDUP(1ul << cidr, FRAMESIZE)); From da336b3ea86521783ef037940e7af01d2945c928 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Wed, 19 Oct 2022 13:10:00 -0700 Subject: [PATCH 3/3] Make some more fixes to prod --- net/http/istestnetip.c | 2 +- net/turfwar/.init.lua | 22 +++++++++++-- net/turfwar/blackholed.c | 3 +- net/turfwar/turfwar.c | 2 -- tool/net/help.txt | 19 +++++++++++ tool/net/redbean.c | 70 ++++++++++++++++++++++++++++------------ 6 files changed, 90 insertions(+), 28 deletions(-) diff --git a/net/http/istestnetip.c b/net/http/istestnetip.c index be57a1d18..273f32e61 100644 --- a/net/http/istestnetip.c +++ b/net/http/istestnetip.c @@ -24,6 +24,6 @@ */ bool IsTestnetIp(uint32_t x) { return (((x & 0xFFFFFF00u) == 0xC0000200u) /* 192.0.2.0/24 */ || - ((x & 0xFFFFFF00u) == 0xC0000200u) /* 198.51.100.0/24 */ || + ((x & 0xFFFFFF00u) == 0x0c6336400) /* 198.51.100.0/24 */ || ((x & 0xFFFFFF00u) == 0xCB007100u) /* 203.0.113.0/24 */); } diff --git a/net/turfwar/.init.lua b/net/turfwar/.init.lua index fd037966f..5380a8ec4 100644 --- a/net/turfwar/.init.lua +++ b/net/turfwar/.init.lua @@ -18,7 +18,6 @@ RELAY_HEADERS_TO_CLIENT = { 'Content-Type', 'Last-Modified', 'Referrer-Policy', - 'Vary', } function OnServerStart() @@ -30,10 +29,27 @@ function OnWorkerStart() assert(unix.setrlimit(unix.RLIMIT_RSS, 2*1024*1024)) assert(unix.setrlimit(unix.RLIMIT_CPU, 2)) assert(unix.unveil(nil, nil)) - assert(unix.pledge("stdio inet", nil, unix.PLEDGE_PENALTY_RETURN_EPERM)) + assert(unix.pledge("stdio inet unix", nil, unix.PLEDGE_PENALTY_RETURN_EPERM)) end function OnHttpRequest() + local ip = GetClientAddr() + if not IsTrustedIp(ip) then + local tok = AcquireToken(ip) + if tok < 2 then + if Blackhole(ip) then + Log(kLogWarn, "banned %s" % {FormatIp(ip)}) + else + Log(kLogWarn, "failed to ban %s" % {FormatIp(ip)}) + end + end + if tok < 30 then + ServeError(429) + SetHeader('Connection', 'close') + Log(kLogWarn, "warned %s who has %d tokens" % {FormatIp(ip), tok}) + return + end + end local url = 'http://127.0.0.1' .. EscapePath(GetPath()) local name = GetParam('name') if name then @@ -49,7 +65,7 @@ function OnHttpRequest() ['Referer'] = GetHeader('Referer'), ['Sec-CH-UA-Platform'] = GetHeader('Sec-CH-UA-Platform'), ['User-Agent'] = GetHeader('User-Agent'), - ['X-Forwarded-For'] = FormatIp(GetClientAddr())}}) + ['X-Forwarded-For'] = FormatIp(ip)}}) if status then SetStatus(status) for k,v in pairs(RELAY_HEADERS_TO_CLIENT) do diff --git a/net/turfwar/blackholed.c b/net/turfwar/blackholed.c index c83f0202b..7440a07d3 100644 --- a/net/turfwar/blackholed.c +++ b/net/turfwar/blackholed.c @@ -46,6 +46,7 @@ #include "libc/sysv/consts/timer.h" #include "libc/time/struct/tm.h" #include "net/http/http.h" +#include "net/http/ip.h" #include "third_party/getopt/getopt.h" #include "third_party/musl/passwd.h" @@ -355,7 +356,7 @@ void WritePid(void) { bool IsMyIp(uint32_t ip) { uint32_t *p; for (p = g_myips; *p; ++p) { - if (ip == *p) { + if (ip == *p && !IsTestnetIp(ip)) { return true; } } diff --git a/net/turfwar/turfwar.c b/net/turfwar/turfwar.c index 71ef1c908..8a82e40ff 100644 --- a/net/turfwar/turfwar.c +++ b/net/turfwar/turfwar.c @@ -1029,8 +1029,6 @@ void *HttpWorker(void *arg) { "Cache-Control: max-age=3600, private\r\n" "Date: "); p = FormatDate(p); - p = stpcpy(p, "\r\nX-Token-Count: "); - p = FormatInt32(p, CountTokens(g_tok.b, ip, TB_CIDR)); p = stpcpy(p, "\r\nContent-Length: "); p = FormatInt32(p, strlen(ipbuf)); p = stpcpy(p, "\r\n\r\n"); diff --git a/tool/net/help.txt b/tool/net/help.txt index 9ee031b39..65f8f305d 100644 --- a/tool/net/help.txt +++ b/tool/net/help.txt @@ -2038,6 +2038,25 @@ FUNCTIONS `ip` should be an IPv4 address and this defaults to GetClientAddr(), although other interpretations of its meaning are possible. + Blackhole(ip:uint32) + └─→ bool + + Sends IP address to blackholed service. + + ProgramTokenBucket() needs to be called beforehand. The default + settings will blackhole automatically, during the accept() loop + based on the banned threshold. However if your Lua code calls + AcquireToken() manually, then you'll need this function to take + action on the returned values. + + This function returns true if a datagram could be sent sucessfully. + Otherwise false is returned, which can happen if blackholed isn't + running, or if a lot of processes are sending messages to it and the + operation would have blocked. + + It's assumed that the blackholed service is running locally in the + background. + ──────────────────────────────────────────────────────────────────────────────── CONSTANTS diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 44712918b..9dfc9fe36 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -897,7 +897,8 @@ static bool IsTrustedIp(uint32_t ip) { uint32_t *p; if (interfaces) { for (p = interfaces; *p; ++p) { - if (ip == *p) { + if (ip == *p && !IsTestnetIp(ip)) { + DEBUGF("(token) ip is trusted because it's %s", "a local interface"); return true; } } @@ -905,12 +906,19 @@ static bool IsTrustedIp(uint32_t ip) { if (trustedips.n) { for (i = 0; i < trustedips.n; ++i) { if ((ip & trustedips.p[i].mask) == trustedips.p[i].ip) { + DEBUGF("(token) ip is trusted because it's %s", "whitelisted"); return true; } } return false; + } else if (IsPrivateIp(ip) && !IsTestnetIp(ip)) { + DEBUGF("(token) ip is trusted because it's %s", "private"); + return true; + } else if (IsLoopbackIp(ip)) { + DEBUGF("(token) ip is trusted because it's %s", "loopback"); + return true; } else { - return IsPrivateIp(ip) || IsLoopbackIp(ip); + return false; } } @@ -4752,20 +4760,34 @@ static int LuaIsAssetCompressed(lua_State *L) { return 1; } -static void Blackhole(uint32_t ip) { +static bool Blackhole(uint32_t ip) { char buf[4]; - if (blackhole.fd > 0) return; + if (blackhole.fd <= 0) return false; WRITE32BE(buf, ip); if (sendto(blackhole.fd, &buf, 4, 0, (struct sockaddr *)&blackhole.addr, - sizeof(blackhole.addr)) == -1) { - VERBOSEF("error: sendto(%s) failed: %m\n", blackhole.addr.sun_path); + sizeof(blackhole.addr)) != -1) { + return true; + } else { + VERBOSEF("(token) sendto(%s) failed: %m", blackhole.addr.sun_path); errno = 0; + return false; } } +static int LuaBlackhole(lua_State *L) { + lua_Integer ip; + ip = luaL_checkinteger(L, 1); + if (!(0 <= ip && ip <= 0xffffffff)) { + luaL_argerror(L, 1, "ip out of range"); + unreachable; + } + lua_pushboolean(L, Blackhole(ip)); + return 1; +} + wontreturn static void Replenisher(void) { struct timespec ts; - VERBOSEF("token replenish worker started"); + VERBOSEF("(token) replenish worker started"); signal(SIGINT, OnTerm); signal(SIGHUP, OnTerm); signal(SIGTERM, OnTerm); @@ -4779,8 +4801,9 @@ wontreturn static void Replenisher(void) { } ReplenishTokens(tokenbucket.w, (1ul << tokenbucket.cidr) / 8); ts = _timespec_add(ts, tokenbucket.replenish); + DEBUGF("(token) replenished tokens"); } - VERBOSEF("token replenish worker exiting"); + VERBOSEF("(token) replenish worker exiting"); _Exit(0); } @@ -4846,7 +4869,7 @@ static int LuaProgramTokenBucket(lua_State *L) { luaL_argerror(L, 5, "require ban <= ignore"); unreachable; } - INFOF("deploying %,ld buckets " + INFOF("(token) deploying %,ld buckets " "(one for every %ld ips) " "each holding 127 tokens which " "replenish %g times per second, " @@ -4867,14 +4890,14 @@ static int LuaProgramTokenBucket(lua_State *L) { strlcpy(blackhole.addr.sun_path, "/var/run/blackhole.sock", sizeof(blackhole.addr.sun_path)); if ((blackhole.fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0)) == -1) { - WARNF("error: socket(AF_UNIX) failed: %m"); + WARNF("(token) socket(AF_UNIX) failed: %m"); ban = -1; } else if (sendto(blackhole.fd, &testip, 4, 0, (struct sockaddr *)&blackhole.addr, sizeof(blackhole.addr)) == -1) { - WARNF("error: sendto(%`'s) failed: %m", blackhole.addr.sun_path); - WARNF("redbean isn't able to protect your kernel from level 4 ddos"); - WARNF("please run the blackholed program, see https://justine.lol/"); + WARNF("(token) error: sendto(%`'s) failed: %m", blackhole.addr.sun_path); + WARNF("(token) redbean isn't able to protect your kernel from ddos"); + WARNF("(token) please run the blackholed program; see our website!"); } } tokenbucket.b = _mapshared(ROUNDUP(1ul << cidr, FRAMESIZE)); @@ -5212,6 +5235,7 @@ static const luaL_Reg kLuaFuncs[] = { {"oct", LuaOct}, // #ifndef UNSECURE {"AcquireToken", LuaAcquireToken}, // + {"Blackhole", LuaBlackhole}, // undocumented {"CountTokens", LuaCountTokens}, // {"EvadeDragnetSurveillance", LuaEvadeDragnetSurveillance}, // {"GetSslIdentity", LuaGetSslIdentity}, // @@ -6122,7 +6146,6 @@ static char *ServeAsset(struct Asset *a, const char *path, size_t pathlen) { } else { return ServeError(500, "Internal Server Error"); } -#if 0 // TODO(jart): re-enable me } else if (!IsTiny() && cpm.msg.method != kHttpHead && !IsSslCompressed() && ClientAcceptsGzip() && !(a->file && @@ -6132,7 +6155,6 @@ static char *ServeAsset(struct Asset *a, const char *path, size_t pathlen) { MeasureEntropy(cpm.content, 1000) < 7))) { WARNF("serving compressed asset"); p = ServeAssetCompressed(a); -#endif } else { p = ServeAssetIdentity(a, ct); } @@ -6700,32 +6722,38 @@ static int HandleConnection(size_t i) { LockInc(&shared->c.accepts); GetClientAddr(&ip, 0); if (tokenbucket.cidr && tokenbucket.reject >= 0) { - if (!IsLoopbackIp(ip) && !IsTrustedIp(ip)) { + if (!IsTrustedIp(ip)) { tok = AcquireToken(tokenbucket.b, ip, tokenbucket.cidr); if (tok <= tokenbucket.ban && tokenbucket.ban >= 0) { - WARNF("(srvr) banning %hhu.%hhu.%hhu.%hhu who only has %d tokens", + WARNF("(token) banning %hhu.%hhu.%hhu.%hhu who only has %d tokens", ip >> 24, ip >> 16, ip >> 8, ip, tok); LockInc(&shared->c.bans); Blackhole(ip); close(client); return 0; } else if (tok <= tokenbucket.ignore && tokenbucket.ignore >= 0) { + DEBUGF("(token) ignoring %hhu.%hhu.%hhu.%hhu who only has %d tokens", + ip >> 24, ip >> 16, ip >> 8, ip, tok); LockInc(&shared->c.ignores); close(client); return 0; } else if (tok < tokenbucket.reject) { - WARNF("(srvr) rejecting %hhu.%hhu.%hhu.%hhu who only has %d tokens", + WARNF("(token) rejecting %hhu.%hhu.%hhu.%hhu who only has %d tokens", ip >> 24, ip >> 16, ip >> 8, ip, tok); LockInc(&shared->c.rejects); SendTooManyRequests(); close(client); return 0; + } else { + DEBUGF("(token) %hhu.%hhu.%hhu.%hhu has %d tokens", ip >> 24, + ip >> 16, ip >> 8, ip, tok - 1); } } else { - DEBUGF( - "(srvr) won't acquire token for whitelisted ip %hhu.%hhu.%hhu.%hhu", - ip >> 24, ip >> 16, ip >> 8, ip); + DEBUGF("(token) won't acquire token for trusted ip %hhu.%hhu.%hhu.%hhu", + ip >> 24, ip >> 16, ip >> 8, ip); } + } else { + DEBUGF("(token) can't acquire accept() token for client"); } startconnection = _timespec_real(); if (UNLIKELY(maxworkers) && shared->workers >= maxworkers) {