diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c558453d5..7de803d3a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,8 @@ name: build +env: + COSMOCC_VERSION: 3.9.2 + on: push: branches: @@ -19,13 +22,48 @@ jobs: matrix: mode: ["", tiny, rel, tinylinux, optlinux] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + # Full checkout needed for git-restore-mtime-bare. + fetch-depth: 0 + + # TODO(jart): fork this action. + - uses: chetan/git-restore-mtime-action@v2 + + - uses: actions/cache/restore@v4 + id: cache + with: + path: | + .cosmocc + o + key: ${{ env.COSMOCC_VERSION }}-${{ matrix.mode }}-${{ github.sha }} + restore-keys: | + ${{ env.COSMOCC_VERSION }}-${{ matrix.mode }}- + ${{ env.COSMOCC_VERSION }}- + + - name: Restore mtimes + if: steps.cache.outputs.cache-hit == 'true' + run: | + while read mtime file; do + [ -f "$file" ] && touch -d "@$mtime" "$file" + done < o/.mtimes - name: support ape bins 1 - run: sudo cp build/bootstrap/ape.elf /usr/bin/ape + run: sudo cp -a build/bootstrap/ape.elf /usr/bin/ape - name: support ape bins 2 run: sudo sh -c "echo ':APE:M::MZqFpD::/usr/bin/ape:' >/proc/sys/fs/binfmt_misc/register" - name: make matrix run: V=0 make -j2 MODE=${{ matrix.mode }} + + - name: Save mtimes + run: | + find o -type f -exec stat -c "%Y %n" {} \; > o/.mtimes + + - uses: actions/cache/save@v4 + with: + path: | + .cosmocc + o + key: ${{ env.COSMOCC_VERSION }}-${{ matrix.mode }}-${{ github.sha }} diff --git a/.gitignore b/.gitignore index 4c767cd51..0c6b21f03 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ __pycache__ /tool/emacs/*.elc /perf.data /perf.data.old +/qemu*core diff --git a/.vscode/settings.json b/.vscode/settings.json index 56c99cef4..6ce0ca591 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "C_Cpp.default.compilerPath": ".cosmocc/3.8.0/bin/aarch64-linux-cosmo-c++", + "C_Cpp.default.compilerPath": ".cosmocc/3.9.2/bin/aarch64-linux-cosmo-c++", "C_Cpp.default.compilerArgs": [ "-nostdinc", "-nostdlib", diff --git a/Makefile b/Makefile index 181021d01..33fbcbdae 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,8 @@ COMMA := , PWD := $(shell pwd) # detect wsl2 running cosmopolitan binaries on the host by checking whether: -# - user ran build/bootstrap/make, in which case make's working directory is in wsl +# - user ran .cosmocc/current/bin/make, in which case make's working directory +# is in wsl # - user ran make, in which case cocmd's working directory is in wsl ifneq ($(findstring //wsl.localhost/,$(CURDIR) $(PWD)),) $(warning wsl2 interop is enabled) @@ -89,7 +90,7 @@ UNAME_S := $(shell uname -s) # apple still distributes a 17 year old version of gnu make ifeq ($(MAKE_VERSION), 3.81) -$(error please use build/bootstrap/make) +$(error please use https://cosmo.zip/pub/cosmos/bin/make) endif LC_ALL = C @@ -135,7 +136,7 @@ ARCH = aarch64 HOSTS ?= pi pi5 studio freebsdarm else ARCH = x86_64 -HOSTS ?= freebsd rhel7 xnu openbsd netbsd win10 +HOSTS ?= freebsd rhel7 xnu openbsd netbsd win10 luna endif ZIPOBJ_FLAGS += -a$(ARCH) @@ -147,10 +148,10 @@ export MODE export SOURCE_DATE_EPOCH export TMPDIR -COSMOCC = .cosmocc/3.8.0 +COSMOCC = .cosmocc/3.9.2 BOOTSTRAP = $(COSMOCC)/bin TOOLCHAIN = $(COSMOCC)/bin/$(ARCH)-linux-cosmo- -DOWNLOAD := $(shell build/download-cosmocc.sh $(COSMOCC) 3.8.0 813c6b2f95062d2e0a845307a79505424cb98cb038e8013334f8a22e3b92a474) +DOWNLOAD := $(shell build/download-cosmocc.sh $(COSMOCC) 3.9.2 f4ff13af65fcd309f3f1cfd04275996fb7f72a4897726628a8c9cf732e850193) IGNORE := $(shell $(MKDIR) $(TMPDIR)) @@ -274,11 +275,16 @@ include libc/BUILD.mk #─┘ include libc/sock/BUILD.mk #─┐ include net/http/BUILD.mk # ├──ONLINE RUNTIME include third_party/musl/BUILD.mk # │ You can communicate with the network +include third_party/regex/BUILD.mk # │ +include third_party/tr/BUILD.mk # │ +include third_party/sed/BUILD.mk # │ +include libc/system/BUILD.mk # │ include libc/x/BUILD.mk # │ include dsp/scale/BUILD.mk # │ include dsp/mpeg/BUILD.mk # │ include dsp/tty/BUILD.mk # │ include dsp/audio/BUILD.mk # │ +include dsp/prog/BUILD.mk # │ include dsp/BUILD.mk # │ include third_party/stb/BUILD.mk # │ include third_party/mbedtls/BUILD.mk # │ @@ -292,8 +298,7 @@ include third_party/libcxx/BUILD.mk # │ include third_party/openmp/BUILD.mk # │ include third_party/pcre/BUILD.mk # │ include third_party/less/BUILD.mk # │ -include net/https/BUILD.mk # │ -include third_party/regex/BUILD.mk #─┘ +include net/https/BUILD.mk #─┘ include third_party/tidy/BUILD.mk include third_party/BUILD.mk include third_party/nsync/testing/BUILD.mk @@ -312,8 +317,6 @@ include third_party/double-conversion/test/BUILD.mk include third_party/lua/BUILD.mk include third_party/tree/BUILD.mk include third_party/zstd/BUILD.mk -include third_party/tr/BUILD.mk -include third_party/sed/BUILD.mk include third_party/awk/BUILD.mk include third_party/hiredis/BUILD.mk include third_party/make/BUILD.mk @@ -366,6 +369,7 @@ include test/libc/fmt/BUILD.mk include test/libc/time/BUILD.mk include test/libc/proc/BUILD.mk include test/libc/stdio/BUILD.mk +include test/libc/system/BUILD.mk include test/libc/BUILD.mk include test/net/http/BUILD.mk include test/net/https/BUILD.mk @@ -432,64 +436,64 @@ loc: o/$(MODE)/tool/build/summy find -name \*.h -or -name \*.hpp -or -name \*.c -or -name \*.cc -or -name \*.cpp -or -name \*.S -or -name \*.mk | \ $(XARGS) wc -l | grep total | awk '{print $$1}' | $< -# PLEASE: MAINTAIN TOPOLOGICAL ORDER -# FROM HIGHEST LEVEL TO LOWEST LEVEL -COSMOPOLITAN_OBJECTS = \ +COSMOPOLITAN = \ CTL \ - THIRD_PARTY_DOUBLECONVERSION \ - THIRD_PARTY_OPENMP \ - TOOL_ARGS \ - NET_HTTP \ DSP_AUDIO \ - LIBC_SOCK \ - LIBC_NT_WS2_32 \ - LIBC_NT_IPHLPAPI \ - LIBC_X \ - THIRD_PARTY_GETOPT \ + LIBC_CALLS \ + LIBC_DLOPEN \ + LIBC_ELF \ + LIBC_FMT \ + LIBC_INTRIN \ + LIBC_IRQ \ LIBC_LOG \ - THIRD_PARTY_TZ \ - THIRD_PARTY_MUSL \ - THIRD_PARTY_ZLIB_GZ \ + LIBC_MEM \ + LIBC_NEXGEN32E \ + LIBC_NT_ADVAPI32 \ + LIBC_NT_BCRYPTPRIMITIVES \ + LIBC_NT_COMDLG32 \ + LIBC_NT_GDI32 \ + LIBC_NT_IPHLPAPI \ + LIBC_NT_KERNEL32 \ + LIBC_NT_NTDLL \ + LIBC_NT_PDH \ + LIBC_NT_POWRPROF \ + LIBC_NT_PSAPI \ + LIBC_NT_REALTIME \ + LIBC_NT_SHELL32 \ + LIBC_NT_SYNCHRONIZATION \ + LIBC_NT_USER32 \ + LIBC_NT_WS2_32 \ + LIBC_PROC \ + LIBC_RUNTIME \ + LIBC_SOCK \ + LIBC_STDIO \ + LIBC_STR \ + LIBC_SYSTEM \ + LIBC_SYSV \ + LIBC_SYSV_CALLS \ + LIBC_THREAD \ + LIBC_TINYMATH \ + LIBC_VGA \ + LIBC_X \ + NET_HTTP \ + THIRD_PARTY_COMPILER_RT \ + THIRD_PARTY_DLMALLOC \ + THIRD_PARTY_DOUBLECONVERSION \ + THIRD_PARTY_GDTOA \ + THIRD_PARTY_GETOPT \ THIRD_PARTY_LIBCXXABI \ THIRD_PARTY_LIBUNWIND \ - LIBC_STDIO \ - THIRD_PARTY_GDTOA \ - THIRD_PARTY_REGEX \ - LIBC_THREAD \ - LIBC_PROC \ - THIRD_PARTY_NSYNC_MEM \ - LIBC_MEM \ - THIRD_PARTY_DLMALLOC \ - LIBC_DLOPEN \ - LIBC_RUNTIME \ + THIRD_PARTY_MUSL \ THIRD_PARTY_NSYNC \ - LIBC_ELF \ - LIBC_IRQ \ - LIBC_CALLS \ - LIBC_SYSV_CALLS \ - LIBC_VGA \ - LIBC_NT_REALTIME \ - LIBC_NT_PSAPI \ - LIBC_NT_POWRPROF \ - LIBC_NT_PDH \ - LIBC_NT_GDI32 \ - LIBC_NT_COMDLG32 \ - LIBC_NT_USER32 \ - LIBC_NT_NTDLL \ - LIBC_NT_ADVAPI32 \ - LIBC_NT_SYNCHRONIZATION \ - LIBC_FMT \ - THIRD_PARTY_ZLIB \ + THIRD_PARTY_NSYNC_MEM \ + THIRD_PARTY_OPENMP \ THIRD_PARTY_PUFF \ - THIRD_PARTY_COMPILER_RT \ - LIBC_TINYMATH \ + THIRD_PARTY_REGEX \ + THIRD_PARTY_TZ \ THIRD_PARTY_XED \ - LIBC_STR \ - LIBC_SYSV \ - LIBC_INTRIN \ - LIBC_NT_BCRYPTPRIMITIVES \ - LIBC_NT_KERNEL32 \ - LIBC_NEXGEN32E + THIRD_PARTY_ZLIB \ + THIRD_PARTY_ZLIB_GZ \ + TOOL_ARGS \ COSMOPOLITAN_H_PKGS = \ APE \ @@ -537,7 +541,7 @@ COSMOCC_PKGS = \ THIRD_PARTY_INTEL o/$(MODE)/cosmopolitan.a: \ - $(foreach x,$(COSMOPOLITAN_OBJECTS),$($(x)_A_OBJS)) + $(call reverse,$(call uniq,$(foreach x,$(COSMOPOLITAN),$($(x))))) COSMOCC_HDRS = \ $(wildcard libc/integral/*) \ diff --git a/README.md b/README.md index d447a47ad..75851c8be 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,12 @@ [![build](https://github.com/jart/cosmopolitan/actions/workflows/build.yml/badge.svg)](https://github.com/jart/cosmopolitan/actions/workflows/build.yml) # Cosmopolitan -[Cosmopolitan Libc](https://justine.lol/cosmopolitan/index.html) makes C +[Cosmopolitan Libc](https://justine.lol/cosmopolitan/index.html) makes C/C++ a build-once run-anywhere language, like Java, except it doesn't need an interpreter or virtual machine. Instead, it reconfigures stock GCC and Clang to output a POSIX-approved polyglot format that runs natively on -Linux + Mac + Windows + FreeBSD + OpenBSD + NetBSD + BIOS with the best -possible performance and the tiniest footprint imaginable. +Linux + Mac + Windows + FreeBSD + OpenBSD 7.3 + NetBSD + BIOS with the +best possible performance and the tiniest footprint imaginable. ## Background @@ -87,15 +87,22 @@ ape/apeinstall.sh ``` You can now build the mono repo with any modern version of GNU Make. To -make life easier, we've included one in the cosmocc toolchain, which is -guaranteed to be compatible and furthermore includes our extensions for -doing build system sandboxing. +bootstrap your build, you can install Cosmopolitan Make from this site: + +https://cosmo.zip/pub/cosmos/bin/make + +E.g.: ```sh -build/bootstrap/make -j8 +curl -LO https://cosmo.zip/pub/cosmos/bin/make +./make -j8 o//examples/hello ``` +After you've built the repo once, you can also use the make from your +cosmocc at `.cosmocc/current/bin/make`. You might even prefer to alias +make to `$COSMO/.cosmocc/current/bin/make`. + Since the Cosmopolitan repository is very large, you might only want to build one particular thing. Here's an example of a target that can be compiled relatively quickly, which is a simple POSIX test that only @@ -103,7 +110,7 @@ depends on core LIBC packages. ```sh rm -rf o//libc o//test -build/bootstrap/make o//test/posix/signal_test +.cosmocc/current/bin/make o//test/posix/signal_test o//test/posix/signal_test ``` @@ -112,21 +119,21 @@ list out each individual one. For example if you wanted to build and run all the unit tests in the `TEST_POSIX` package, you could say: ```sh -build/bootstrap/make o//test/posix +.cosmocc/current/bin/make o//test/posix ``` Cosmopolitan provides a variety of build modes. For example, if you want really tiny binaries (as small as 12kb in size) then you'd say: ```sh -build/bootstrap/make m=tiny +.cosmocc/current/bin/make m=tiny ``` You can furthermore cut out the bloat of other operating systems, and have Cosmopolitan become much more similar to Musl Libc. ```sh -build/bootstrap/make m=tinylinux +.cosmocc/current/bin/make m=tinylinux ``` For further details, see [//build/config.mk](build/config.mk). @@ -242,16 +249,16 @@ server. You're welcome to join us! ## Support Vector -| Platform | Min Version | Circa | -| :--- | ---: | ---: | -| AMD | K8 | 2003 | -| Intel | Core | 2006 | -| Linux | 2.6.18 | 2007 | -| Windows | 8 [1] | 2012 | -| Darwin (macOS) | 23.1.0+ | 2023 | -| OpenBSD | 7 | 2021 | -| FreeBSD | 13 | 2020 | -| NetBSD | 9.2 | 2021 | +| Platform | Min Version | Circa | +| :--- | ---: | ---: | +| AMD | K8 | 2003 | +| Intel | Core | 2006 | +| Linux | 2.6.18 | 2007 | +| Windows | 8 [1] | 2012 | +| Darwin (macOS) | 23.1.0+ | 2023 | +| OpenBSD | 7.3 or earlier | 2023 | +| FreeBSD | 13 | 2020 | +| NetBSD | 9.2 | 2021 | [1] See our [vista branch](https://github.com/jart/cosmopolitan/tree/vista) for a community supported version of Cosmopolitan that works on Windows diff --git a/ape/aarch64.lds b/ape/aarch64.lds index 0a232a2da..48562d2a2 100644 --- a/ape/aarch64.lds +++ b/ape/aarch64.lds @@ -259,6 +259,9 @@ SECTIONS { .debug_ranges 0 : { *(.debug_ranges) } .debug_macro 0 : { *(.debug_macro) } .debug_addr 0 : { *(.debug_addr) } + .debug_names 0 : { *(.debug_names) } + .debug_loclists 0 : { *(.debug_loclists) } + .debug_str_offsets 0 : { *(.debug_str_offsets) } .ARM.attributes 0 : { KEEP(*(.ARM.attributes)) KEEP(*(.gnu.attributes)) } .note.gnu.arm.ident 0 : { KEEP(*(.note.gnu.arm.ident)) } diff --git a/ape/ape.lds b/ape/ape.lds index ec63ae7d5..ac82bde00 100644 --- a/ape/ape.lds +++ b/ape/ape.lds @@ -386,6 +386,13 @@ SECTIONS { _tbss_end = .; } :Tls + .eh_frame : { + __eh_frame_start = .; + KEEP(*(.eh_frame)) + *(.eh_frame.*) + __eh_frame_end = .; + } :Ram + .data . : { /*BEGIN: Read/Write Data */ #if SupportsWindows() @@ -426,11 +433,6 @@ SECTIONS { KEEP(*(.dtors)) __fini_array_end = .; - __eh_frame_start = .; - KEEP(*(.eh_frame)) - *(.eh_frame.*) - __eh_frame_end = .; - /*BEGIN: Post-Initialization Read-Only */ . = ALIGN(. != 0 ? __SIZEOF_POINTER__ : 0); KEEP(*(SORT_BY_NAME(.piro.relo.sort.*))) @@ -439,7 +441,6 @@ SECTIONS { KEEP(*(.piro.pad.data)) *(.igot.plt) KEEP(*(.dataepilogue)) - . = ALIGN(. != 0 ? CONSTANT(COMMONPAGESIZE) : 0); /*END: NT FORK COPYING */ _edata = .; @@ -519,6 +520,9 @@ SECTIONS { .debug_rnglists 0 : { *(.debug_rnglists) } .debug_macro 0 : { *(.debug_macro) } .debug_addr 0 : { *(.debug_addr) } + .debug_names 0 : { *(.debug_names) } + .debug_loclists 0 : { *(.debug_loclists) } + .debug_str_offsets 0 : { *(.debug_str_offsets) } .gnu.attributes 0 : { KEEP(*(.gnu.attributes)) } .GCC.command.line 0 : { *(.GCC.command.line) } @@ -582,11 +586,11 @@ ape_rom_memsz = ape_rom_filesz; ape_rom_align = CONSTANT(COMMONPAGESIZE); ape_rom_rva = RVA(ape_rom_vaddr); -ape_ram_vaddr = ADDR(.data); +ape_ram_vaddr = ADDR(.eh_frame); ape_ram_offset = ape_ram_vaddr - __executable_start; -ape_ram_paddr = LOADADDR(.data); -ape_ram_filesz = ADDR(.bss) - ADDR(.data); -ape_ram_memsz = _end - ADDR(.data); +ape_ram_paddr = LOADADDR(.eh_frame); +ape_ram_filesz = ADDR(.bss) - ADDR(.eh_frame); +ape_ram_memsz = _end - ADDR(.eh_frame); ape_ram_align = CONSTANT(COMMONPAGESIZE); ape_ram_rva = RVA(ape_ram_vaddr); @@ -596,7 +600,7 @@ ape_stack_offset = 0; ape_stack_vaddr = DEFINED(ape_stack_vaddr) ? ape_stack_vaddr : 0x700000000000; ape_stack_paddr = ape_ram_paddr + ape_ram_filesz; ape_stack_filesz = 0; -ape_stack_memsz = DEFINED(ape_stack_memsz) ? ape_stack_memsz : 8 * 1024 * 1024; +ape_stack_memsz = DEFINED(ape_stack_memsz) ? ape_stack_memsz : 4 * 1024 * 1024; ape_note_offset = ape_cod_offset + (ape_note - ape_cod_vaddr); ape_note_filesz = ape_note_end - ape_note; diff --git a/ape/apeinstall.sh b/ape/apeinstall.sh index 2a0a28590..73f24965f 100755 --- a/ape/apeinstall.sh +++ b/ape/apeinstall.sh @@ -10,8 +10,8 @@ if [ ! -f ape/loader.c ]; then cd "$COSMO" || exit fi -if [ -x build/bootstrap/make ]; then - MAKE=build/bootstrap/make +if [ -x .cosmocc/current/bin/make ]; then + MAKE=.cosmocc/current/bin/make else MAKE=make fi diff --git a/build/download-cosmocc.sh b/build/download-cosmocc.sh index 13310a4e4..52c89b091 100755 --- a/build/download-cosmocc.sh +++ b/build/download-cosmocc.sh @@ -99,3 +99,8 @@ rm -f cosmocc.zip cosmocc.zip.sha256sum # commit output directory cd "${OLDPWD}" || die mv "${OUTPUT_TMP}" "${OUTPUT_DIR}" || die + +# update current symlink +BASE=$(basename "${OUTPUT_DIR}") +DIR=$(dirname "${OUTPUT_DIR}") +ln -sfn "$BASE" "$DIR/current" diff --git a/build/objdump b/build/objdump index b49667976..358d8f4c8 100755 --- a/build/objdump +++ b/build/objdump @@ -6,14 +6,14 @@ if [ -n "$OBJDUMP" ]; then fi find_objdump() { - if [ -x .cosmocc/3.8.0/bin/$1-linux-cosmo-objdump ]; then - OBJDUMP=.cosmocc/3.8.0/bin/$1-linux-cosmo-objdump - elif [ -x .cosmocc/3.8.0/bin/$1-linux-musl-objdump ]; then - OBJDUMP=.cosmocc/3.8.0/bin/$1-linux-musl-objdump - elif [ -x "$COSMO/.cosmocc/3.8.0/bin/$1-linux-cosmo-objdump" ]; then - OBJDUMP="$COSMO/.cosmocc/3.8.0/bin/$1-linux-cosmo-objdump" - elif [ -x "$COSMO/.cosmocc/3.8.0/bin/$1-linux-musl-objdump" ]; then - OBJDUMP="$COSMO/.cosmocc/3.8.0/bin/$1-linux-musl-objdump" + if [ -x .cosmocc/3.9.2/bin/$1-linux-cosmo-objdump ]; then + OBJDUMP=.cosmocc/3.9.2/bin/$1-linux-cosmo-objdump + elif [ -x .cosmocc/3.9.2/bin/$1-linux-musl-objdump ]; then + OBJDUMP=.cosmocc/3.9.2/bin/$1-linux-musl-objdump + elif [ -x "$COSMO/.cosmocc/3.9.2/bin/$1-linux-cosmo-objdump" ]; then + OBJDUMP="$COSMO/.cosmocc/3.9.2/bin/$1-linux-cosmo-objdump" + elif [ -x "$COSMO/.cosmocc/3.9.2/bin/$1-linux-musl-objdump" ]; then + OBJDUMP="$COSMO/.cosmocc/3.9.2/bin/$1-linux-musl-objdump" else echo "error: toolchain not found (try running 'cosmocc --update' or 'make' in the cosmo monorepo)" >&2 exit 1 diff --git a/ctl/shared_ptr.h b/ctl/shared_ptr.h index 8bb892943..8aac68070 100644 --- a/ctl/shared_ptr.h +++ b/ctl/shared_ptr.h @@ -5,6 +5,7 @@ #include "exception.h" #include "is_base_of.h" +#include "is_constructible.h" #include "is_convertible.h" #include "remove_extent.h" #include "unique_ptr.h" @@ -96,12 +97,12 @@ class shared_ref size_t use_count() const noexcept { - return shared + 1; + return __atomic_load_n(&shared, __ATOMIC_RELAXED) + 1; } size_t weak_count() const noexcept { - return weak; + return __atomic_load_n(&weak, __ATOMIC_RELAXED); } private: @@ -348,7 +349,7 @@ class shared_ptr template bool owner_before(const weak_ptr& r) const noexcept { - return !r.owner_before(*this); + return rc < r.rc; } private: @@ -381,6 +382,34 @@ class weak_ptr rc->keep_weak(); } + weak_ptr(const weak_ptr& r) noexcept : p(r.p), rc(r.rc) + { + if (rc) + rc->keep_weak(); + } + + template + requires __::shared_ptr_compatible + weak_ptr(const weak_ptr& r) noexcept : p(r.p), rc(r.rc) + { + if (rc) + rc->keep_weak(); + } + + weak_ptr(weak_ptr&& r) noexcept : p(r.p), rc(r.rc) + { + r.p = nullptr; + r.rc = nullptr; + } + + template + requires __::shared_ptr_compatible + weak_ptr(weak_ptr&& r) noexcept : p(r.p), rc(r.rc) + { + r.p = nullptr; + r.rc = nullptr; + } + ~weak_ptr() { if (rc) @@ -409,6 +438,19 @@ class weak_ptr swap(rc, r.rc); } + weak_ptr& operator=(weak_ptr r) noexcept + { + swap(r); + return *this; + } + + template + requires __::shared_ptr_compatible + weak_ptr& operator=(weak_ptr r) noexcept + { + weak_ptr(move(r)).swap(*this); + } + shared_ptr lock() const noexcept { if (expired()) @@ -437,6 +479,9 @@ class weak_ptr template friend class shared_ptr; + template + friend shared_ptr make_shared(Args&&...); + element_type* p = nullptr; __::shared_ref* rc = nullptr; }; @@ -497,19 +542,75 @@ shared_ptr::shared_ptr(U* const p, D d) } } +// Our make_shared supports passing a weak self reference as the first parameter +// to your constructor, e.g.: +// +// struct Tree : ctl::weak_self_base +// { +// ctl::shared_ptr l, r; +// ctl::weak_ptr parent; +// Tree(weak_ptr const& self, auto&& l2, auto&& r2) +// : l(ctl::forward(l2)), +// r(ctl::forward(r2)) +// { +// if (l) l->parent = self; +// if (r) r->parent = self; +// } +// }; +// +// int main() { +// auto t = ctl::make_shared( +// ctl::make_shared(nullptr, nullptr), nullptr); +// return t->l->parent.lock().get() == t.get() ? 0 : 1; +// } +// +// As shown, passing the parameter at object construction time lets you complete +// object construction without needing a separate Init method. But because we go +// off spec as far as the STL is concerned, there is a potential ambiguity where +// you might have a constructor with a weak_ptr first parameter that is intended +// to be something other than a self-reference. So this feature is opt-in by way +// of inheriting from the following struct. +struct weak_self_base +{}; + template shared_ptr make_shared(Args&&... args) { - auto rc = __::shared_emplace::make(); - rc->construct(forward(args)...); - shared_ptr r; - r.p = &rc->t; - r.rc = rc.release(); - if constexpr (is_base_of_v, T>) { - r->weak_this = r; + unique_ptr rc = __::shared_emplace::make(); + if constexpr (is_base_of_v && + is_constructible_v&, Args...>) { + // A __::shared_ref has a virtual weak reference that is owned by all of + // the shared references. We can avoid some unnecessary refcount changes + // by "borrowing" that reference and passing it to the constructor, then + // promoting it to a shared reference by swapping it with the shared_ptr + // that we return. + weak_ptr w; + w.p = &rc->t; + w.rc = rc.get(); + try { + rc->construct(const_cast&>(w), + forward(args)...); + } catch (...) { + w.p = nullptr; + w.rc = nullptr; + throw; + } + rc.release(); + shared_ptr r; + swap(r.p, w.p); + swap(r.rc, w.rc); + return r; + } else { + rc->construct(forward(args)...); + shared_ptr r; + r.p = &rc->t; + r.rc = rc.release(); + if constexpr (is_base_of_v, T>) { + r->weak_this = r; + } + return r; } - return r; } } // namespace ctl diff --git a/ctl/string.cc b/ctl/string.cc index c30bf699d..5e14220de 100644 --- a/ctl/string.cc +++ b/ctl/string.cc @@ -383,4 +383,72 @@ string::erase(const size_t pos, size_t count) noexcept return *this; } +void +string::append(const ctl::string_view& s, size_t pos, size_t count) noexcept +{ + append(s.substr(pos, count)); +} + +size_t +string::find_last_of(char c, size_t pos) const noexcept +{ + const char* b = data(); + size_t n = size(); + if (pos > n) + pos = n; + const char* p = (const char*)memrchr(b, c, pos); + return p ? p - b : npos; +} + +size_t +string::find_last_of(ctl::string_view set, size_t pos) const noexcept +{ + if (empty() || set.empty()) + return npos; + bool lut[256] = {}; + for (char c : set) + lut[c & 255] = true; + const char* b = data(); + size_t last = size() - 1; + if (pos > last) + pos = last; + for (;;) { + if (lut[b[pos] & 255]) + return pos; + if (!pos) + return npos; + --pos; + } +} + +size_t +string::find_first_of(char c, size_t pos) const noexcept +{ + size_t n = size(); + if (pos >= n) + return npos; + const char* b = data(); + const char* p = (const char*)memchr(b + pos, c, n - pos); + return p ? p - b : npos; +} + +size_t +string::find_first_of(ctl::string_view set, size_t pos) const noexcept +{ + if (set.empty()) + return npos; + bool lut[256] = {}; + for (char c : set) + lut[c & 255] = true; + const char* b = data(); + size_t n = size(); + for (;;) { + if (pos >= n) + return npos; + if (lut[b[pos] & 255]) + return pos; + ++pos; + } +} + } // namespace ctl diff --git a/ctl/string.h b/ctl/string.h index 6c92d8f9f..e6e736eec 100644 --- a/ctl/string.h +++ b/ctl/string.h @@ -125,6 +125,7 @@ class string void append(char, size_t) noexcept; void append(unsigned long) noexcept; void append(const void*, size_t) noexcept; + void append(const ctl::string_view&, size_t, size_t = npos) noexcept; string& insert(size_t, ctl::string_view) noexcept; string& erase(size_t = 0, size_t = npos) noexcept; string substr(size_t = 0, size_t = npos) const noexcept; @@ -136,6 +137,10 @@ class string bool starts_with(ctl::string_view) const noexcept; size_t find(char, size_t = 0) const noexcept; size_t find(ctl::string_view, size_t = 0) const noexcept; + size_t find_first_of(char, size_t = 0) const noexcept; + size_t find_first_of(ctl::string_view, size_t = 0) const noexcept; + size_t find_last_of(char, size_t = npos) const noexcept; + size_t find_last_of(ctl::string_view, size_t = npos) const noexcept; void swap(string& s) noexcept { @@ -302,7 +307,7 @@ class string append(ch); } - void append(const ctl::string_view s) noexcept + void append(const ctl::string_view& s) noexcept { append(s.p, s.n); } diff --git a/ctl/string_view.cc b/ctl/string_view.cc index 951f707a9..3dbadbe21 100644 --- a/ctl/string_view.cc +++ b/ctl/string_view.cc @@ -108,4 +108,66 @@ string_view::starts_with(const string_view s) const noexcept return !memcmp(p, s.p, s.n); } +size_t +string_view::find_last_of(char c, size_t pos) const noexcept +{ + const char* b = data(); + size_t n = size(); + if (pos > n) + pos = n; + const char* p = (const char*)memrchr(b, c, pos); + return p ? p - b : npos; +} + +size_t +string_view::find_last_of(ctl::string_view set, size_t pos) const noexcept +{ + if (empty() || set.empty()) + return npos; + bool lut[256] = {}; + for (char c : set) + lut[c & 255] = true; + const char* b = data(); + size_t last = size() - 1; + if (pos > last) + pos = last; + for (;;) { + if (lut[b[pos] & 255]) + return pos; + if (!pos) + return npos; + --pos; + } +} + +size_t +string_view::find_first_of(char c, size_t pos) const noexcept +{ + size_t n = size(); + if (pos >= n) + return npos; + const char* b = data(); + const char* p = (const char*)memchr(b + pos, c, n - pos); + return p ? p - b : npos; +} + +size_t +string_view::find_first_of(ctl::string_view set, size_t pos) const noexcept +{ + if (set.empty()) + return npos; + bool lut[256] = {}; + for (char c : set) + lut[c & 255] = true; + const char* b = data(); + size_t n = size(); + for (;;) { + if (pos >= n) + return npos; + if (lut[b[pos] & 255]) + return pos; + ++pos; + } +} + } // namespace ctl diff --git a/ctl/string_view.h b/ctl/string_view.h index 64a895799..9c5949f02 100644 --- a/ctl/string_view.h +++ b/ctl/string_view.h @@ -45,6 +45,10 @@ struct string_view string_view substr(size_t = 0, size_t = npos) const noexcept; size_t find(char, size_t = 0) const noexcept; size_t find(string_view, size_t = 0) const noexcept; + size_t find_first_of(char, size_t = 0) const noexcept; + size_t find_first_of(ctl::string_view, size_t = 0) const noexcept; + size_t find_last_of(char, size_t = npos) const noexcept; + size_t find_last_of(ctl::string_view, size_t = npos) const noexcept; constexpr string_view& operator=(const string_view s) noexcept { @@ -109,12 +113,12 @@ struct string_view return p[n - 1]; } - constexpr const_iterator begin() noexcept + constexpr const_iterator begin() const noexcept { return p; } - constexpr const_iterator end() noexcept + constexpr const_iterator end() const noexcept { return p + n; } diff --git a/dsp/BUILD.mk b/dsp/BUILD.mk index 9fff28d51..ae2150196 100644 --- a/dsp/BUILD.mk +++ b/dsp/BUILD.mk @@ -6,4 +6,5 @@ o/$(MODE)/dsp: o/$(MODE)/dsp/audio \ o/$(MODE)/dsp/core \ o/$(MODE)/dsp/mpeg \ o/$(MODE)/dsp/scale \ + o/$(MODE)/dsp/prog \ o/$(MODE)/dsp/tty diff --git a/dsp/audio/cosmoaudio/cosmoaudio.c b/dsp/audio/cosmoaudio/cosmoaudio.c index bff7d14cf..e518a4852 100644 --- a/dsp/audio/cosmoaudio/cosmoaudio.c +++ b/dsp/audio/cosmoaudio/cosmoaudio.c @@ -469,16 +469,16 @@ COSMOAUDIO_ABI int cosmoaudio_poll(struct CosmoAudio* ca, if (in_out_writeFrames && 1u + *in_out_writeFrames > ca->outputBufferFrames) return COSMOAUDIO_ENOBUF; for (;;) { - int done = 0; + int done = 1; ma_uint32 readable = 0; ma_uint32 writable = 0; if (in_out_readFrames) { readable = ma_pcm_rb_available_read(&ca->input); - done |= readable >= (ma_uint32)*in_out_readFrames; + done &= readable >= (ma_uint32)*in_out_readFrames; } if (in_out_writeFrames) { writable = ma_pcm_rb_available_write(&ca->output); - done |= writable >= (ma_uint32)*in_out_writeFrames; + done &= writable >= (ma_uint32)*in_out_writeFrames; } if (done) { if (in_out_readFrames) diff --git a/dsp/prog/BUILD.mk b/dsp/prog/BUILD.mk new file mode 100644 index 000000000..adc0668ee --- /dev/null +++ b/dsp/prog/BUILD.mk @@ -0,0 +1,43 @@ +#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐ +#── vi: set noet ft=make ts=8 sw=8 fenc=utf-8 :vi ────────────────────┘ + +PKGS += DSP_PROG + +DSP_PROG_FILES := $(wildcard dsp/prog/*) +DSP_PROG_HDRS = $(filter %.h,$(DSP_PROG_FILES)) +DSP_PROG_SRCS = $(filter %.c,$(DSP_PROG_FILES)) +DSP_PROG_OBJS = $(DSP_PROG_SRCS:%.c=o/$(MODE)/%.o) +DSP_PROG_COMS = $(DSP_PROG_SRCS:%.c=o/$(MODE)/%) +DSP_PROG_BINS = $(DSP_PROG_COMS) $(DSP_PROG_COMS:%=%.dbg) + +DSP_PROG_DIRECTDEPS = \ + DSP_AUDIO \ + LIBC_CALLS \ + LIBC_INTRIN \ + LIBC_NEXGEN32E \ + LIBC_RUNTIME \ + LIBC_SOCK \ + LIBC_STDIO \ + LIBC_SYSV \ + LIBC_TINYMATH \ + THIRD_PARTY_MUSL \ + +DSP_PROG_DEPS := \ + $(call uniq,$(foreach x,$(DSP_PROG_DIRECTDEPS),$($(x)))) + +o/$(MODE)/dsp/prog/prog.pkg: \ + $(DSP_PROG_OBJS) \ + $(foreach x,$(DSP_PROG_DIRECTDEPS),$($(x)_A).pkg) + +o/$(MODE)/dsp/prog/%.dbg: \ + $(DSP_PROG_DEPS) \ + o/$(MODE)/dsp/prog/prog.pkg \ + o/$(MODE)/dsp/prog/%.o \ + $(CRT) \ + $(APE_NO_MODIFY_SELF) + @$(APELINK) + +$(DSP_PROG_OBJS): dsp/prog/BUILD.mk + +.PHONY: o/$(MODE)/dsp/prog +o/$(MODE)/dsp/prog: $(DSP_PROG_BINS) diff --git a/dsp/prog/loudness.h b/dsp/prog/loudness.h new file mode 100644 index 000000000..75a1e9518 --- /dev/null +++ b/dsp/prog/loudness.h @@ -0,0 +1,41 @@ +#ifndef COSMOPOLITAN_DSP_PROG_LOUDNESS_H_ +#define COSMOPOLITAN_DSP_PROG_LOUDNESS_H_ +#include +#include + +#define MIN_DECIBEL -60 +#define MAX_DECIBEL 0 + +// computes root of mean squares +static double rms(float *p, int n) { + double s = 0; + for (int i = 0; i < n; ++i) + s += p[i] * p[i]; + return sqrt(s / n); +} + +// converts rms to decibel +static double rms_to_db(double rms) { + double db = 20 * log10(rms); + db = fmin(db, MAX_DECIBEL); + db = fmax(db, MIN_DECIBEL); + return db; +} + +// char meter[21]; +// format_decibel_meter(meter, 20, rms_to_db(rms(samps, count))) +static char *format_decibel_meter(char *meter, int width, double db) { + double range = MAX_DECIBEL - MIN_DECIBEL; + int filled = (db - MIN_DECIBEL) / range * width; + for (int i = 0; i < width; ++i) { + if (i < filled) { + meter[i] = '='; + } else { + meter[i] = ' '; + } + } + meter[width] = 0; + return meter; +} + +#endif /* COSMOPOLITAN_DSP_PROG_LOUDNESS_H_ */ diff --git a/dsp/prog/recvaudio.c b/dsp/prog/recvaudio.c new file mode 100644 index 000000000..85ef98ed8 --- /dev/null +++ b/dsp/prog/recvaudio.c @@ -0,0 +1,127 @@ +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "loudness.h" + +/** + * @fileoverview plays audio from remote computer on speaker + * @see dsp/prog/sendaudio.c + */ + +#define SAMPLING_RATE 44100 +#define FRAMES_PER_SECOND 60 +#define DEBUG_LOG 0 +#define PORT 9834 + +#define CHUNK_FRAMES (SAMPLING_RATE / FRAMES_PER_SECOND) + +static_assert(CHUNK_FRAMES * sizeof(short) < 1472, + "audio chunks won't fit in udp ethernet packet"); + +sig_atomic_t g_done; + +void onsig(int sig) { + g_done = 1; +} + +short toshort(float x) { + return fmaxf(-1, fminf(1, x)) * 32767; +} + +float tofloat(short x) { + return x / 32768.f; +} + +int main(int argc, char* argv[]) { + + // listen on udp port for audio + int server; + if ((server = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { + perror("socket"); + return 3; + } + struct sockaddr_in addr = {.sin_family = AF_INET, .sin_port = htons(PORT)}; + if (bind(server, (struct sockaddr*)&addr, sizeof(addr))) { + perror("bind"); + return 4; + } + + // setup signals + struct sigaction sa; + sa.sa_flags = 0; + sa.sa_handler = onsig; + sigemptyset(&sa.sa_mask); + sigaction(SIGINT, &sa, 0); + + // configure cosmo audio + struct CosmoAudioOpenOptions cao = {0}; + cao.sizeofThis = sizeof(struct CosmoAudioOpenOptions); + cao.deviceType = kCosmoAudioDeviceTypePlayback; + cao.sampleRate = SAMPLING_RATE; + cao.bufferFrames = CHUNK_FRAMES * 2; + cao.debugLog = DEBUG_LOG; + cao.channels = 1; + + // connect to microphone and speaker + int status; + struct CosmoAudio* ca; + status = cosmoaudio_open(&ca, &cao); + if (status != COSMOAUDIO_SUCCESS) { + fprintf(stderr, "failed to open audio: %d\n", status); + return 5; + } + + while (!g_done) { + // read from network + ssize_t got; + short buf16[CHUNK_FRAMES]; + if ((got = read(server, buf16, CHUNK_FRAMES * sizeof(short))) == -1) { + if (errno == EINTR) + continue; + perror("read"); + return 7; + } + if (got != CHUNK_FRAMES * sizeof(short)) { + fprintf(stderr, "warning: got partial audio frame\n"); + continue; + } + + // write to speaker + float buf32[CHUNK_FRAMES]; + for (int i = 0; i < CHUNK_FRAMES; ++i) + buf32[i] = tofloat(buf16[i]); + cosmoaudio_poll(ca, 0, (int[]){CHUNK_FRAMES}); + cosmoaudio_write(ca, buf32, CHUNK_FRAMES); + + // print loudness in ascii + char meter[21]; + double db = rms_to_db(rms(buf32, CHUNK_FRAMES)); + format_decibel_meter(meter, 20, db); + printf("\r%s| %+6.2f dB", meter, db); + fflush(stdout); + } + + // clean up resources + cosmoaudio_flush(ca); + cosmoaudio_close(ca); + close(server); +} diff --git a/dsp/prog/sendaudio.c b/dsp/prog/sendaudio.c new file mode 100644 index 000000000..436bbfcdb --- /dev/null +++ b/dsp/prog/sendaudio.c @@ -0,0 +1,149 @@ +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "loudness.h" + +/** + * @fileoverview sends audio from microphone to remote computer + * @see dsp/prog/recvaudio.c + */ + +#define SAMPLING_RATE 44100 +#define FRAMES_PER_SECOND 60 +#define DEBUG_LOG 0 +#define PORT 9834 + +#define CHUNK_FRAMES (SAMPLING_RATE / FRAMES_PER_SECOND) + +static_assert(CHUNK_FRAMES * sizeof(short) < 1472, + "audio chunks won't fit in udp ethernet packet"); + +sig_atomic_t g_done; + +void onsig(int sig) { + g_done = 1; +} + +short toshort(float x) { + return fmaxf(-1, fminf(1, x)) * 32767; +} + +float tofloat(short x) { + return x / 32768.f; +} + +uint32_t host2ip(const char* host) { + uint32_t ip; + if ((ip = inet_addr(host)) != -1u) + return ip; + int rc; + struct addrinfo* ai = NULL; + struct addrinfo hint = {AI_NUMERICSERV, AF_INET, SOCK_STREAM, IPPROTO_TCP}; + if ((rc = getaddrinfo(host, "0", &hint, &ai))) { + fprintf(stderr, "%s: %s\n", host, gai_strerror(rc)); + exit(50 + rc); + } + ip = ntohl(((struct sockaddr_in*)ai->ai_addr)->sin_addr.s_addr); + freeaddrinfo(ai); + return ip; +} + +int main(int argc, char* argv[]) { + + if (argc != 2) { + fprintf(stderr, "%s: missing host argument\n", argv[0]); + return 1; + } + + // get host argument + const char* remote_host = argv[1]; + uint32_t ip = host2ip(remote_host); + + // connect to server + int client; + if ((client = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { + perror(remote_host); + return 3; + } + struct sockaddr_in addr = {.sin_family = AF_INET, + .sin_port = htons(PORT), + .sin_addr.s_addr = htonl(ip)}; + if (connect(client, (struct sockaddr*)&addr, sizeof(addr))) { + perror(remote_host); + return 4; + } + + // setup signals + struct sigaction sa; + sa.sa_flags = 0; + sa.sa_handler = onsig; + sigemptyset(&sa.sa_mask); + sigaction(SIGINT, &sa, 0); + + // configure cosmo audio + struct CosmoAudioOpenOptions cao = {0}; + cao.sizeofThis = sizeof(struct CosmoAudioOpenOptions); + cao.deviceType = kCosmoAudioDeviceTypeCapture; + cao.sampleRate = SAMPLING_RATE; + cao.bufferFrames = CHUNK_FRAMES * 2; + cao.debugLog = DEBUG_LOG; + cao.channels = 1; + + // connect to microphone and speaker + int status; + struct CosmoAudio* ca; + status = cosmoaudio_open(&ca, &cao); + if (status != COSMOAUDIO_SUCCESS) { + fprintf(stderr, "failed to open audio: %d\n", status); + return 5; + } + + while (!g_done) { + // read from microphone + float buf32[CHUNK_FRAMES]; + cosmoaudio_poll(ca, (int[]){CHUNK_FRAMES}, 0); + cosmoaudio_read(ca, buf32, CHUNK_FRAMES); + short buf16[CHUNK_FRAMES]; + for (int i = 0; i < CHUNK_FRAMES; ++i) + buf16[i] = toshort(buf32[i]); + + // send to server + if (write(client, buf16, CHUNK_FRAMES * sizeof(short)) == -1) { + if (errno == EINTR && g_done) + break; + perror(remote_host); + return 7; + } + + // print loudness in ascii + char meter[21]; + double db = rms_to_db(rms(buf32, CHUNK_FRAMES)); + format_decibel_meter(meter, 20, db); + printf("\r%s| %+6.2f dB", meter, db); + fflush(stdout); + } + + // clean up resources + cosmoaudio_close(ca); + close(client); +} diff --git a/examples/BUILD.mk b/examples/BUILD.mk index b5dd4b2f4..3d50b5429 100644 --- a/examples/BUILD.mk +++ b/examples/BUILD.mk @@ -54,8 +54,8 @@ EXAMPLES_DIRECTDEPS = \ LIBC_NEXGEN32E \ LIBC_NT_ADVAPI32 \ LIBC_NT_IPHLPAPI \ - LIBC_NT_MEMORY \ LIBC_NT_KERNEL32 \ + LIBC_NT_MEMORY \ LIBC_NT_NTDLL \ LIBC_NT_USER32 \ LIBC_NT_WS2_32 \ @@ -64,6 +64,7 @@ EXAMPLES_DIRECTDEPS = \ LIBC_SOCK \ LIBC_STDIO \ LIBC_STR \ + LIBC_SYSTEM \ LIBC_SYSV \ LIBC_SYSV_CALLS \ LIBC_TESTLIB \ @@ -81,6 +82,8 @@ EXAMPLES_DIRECTDEPS = \ THIRD_PARTY_GETOPT \ THIRD_PARTY_HIREDIS \ THIRD_PARTY_LIBCXX \ + THIRD_PARTY_LIBCXXABI \ + THIRD_PARTY_LIBUNWIND \ THIRD_PARTY_LINENOISE \ THIRD_PARTY_LUA \ THIRD_PARTY_MBEDTLS \ @@ -94,12 +97,10 @@ EXAMPLES_DIRECTDEPS = \ THIRD_PARTY_TZ \ THIRD_PARTY_VQSORT \ THIRD_PARTY_XED \ - THIRD_PARTY_LIBCXXABI \ - THIRD_PARTY_LIBUNWIND \ THIRD_PARTY_ZLIB \ TOOL_ARGS \ TOOL_BUILD_LIB \ - TOOL_VIZ_LIB + TOOL_VIZ_LIB \ EXAMPLES_DEPS := \ $(call uniq,$(foreach x,$(EXAMPLES_DIRECTDEPS),$($(x)))) @@ -150,6 +151,10 @@ o/$(MODE)/examples/picol.o: private \ CPPFLAGS += \ -DSTACK_FRAME_UNLIMITED +o/$(MODE)/examples/nesemu1.o: private \ + CPPFLAGS += \ + -O3 + o/$(MODE)/examples/picol.dbg: \ $(EXAMPLES_DEPS) \ o/$(MODE)/examples/picol.o \ diff --git a/examples/art.c b/examples/art.c new file mode 100644 index 000000000..3802afa9b --- /dev/null +++ b/examples/art.c @@ -0,0 +1,353 @@ +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @fileoverview program for viewing bbs art files + * @see https://github.com/blocktronics/artpacks + * @see http://www.textfiles.com/art/ + */ + +#define HELP \ + "Usage:\n\ + art [-b %d] [-f %s] [-t %s] FILE...\n\ +\n\ +Flags:\n\ + -b NUMBER specifies simulated modem baud rate, which defaults to\n\ + 2400 since that was the most common modem speed in the\n\ + later half of the 1980s during the BBS golden age; you\n\ + could also say 300 for the slowest experience possible\n\ + or you could say 14.4k to get more of a 90's feel, and\n\ + there's also the infamous 56k to bring you back to y2k\n\ + -f CHARSET specifies charset of input bytes, where the default is\n\ + cp347 which means IBM Code Page 347 a.k.a. DOS\n\ + -t CHARSET specifies output charset used by your terminal, and it\n\ + defaults to utf8 a.k.a. thompson-pike encoding\n\ +\n\ +Supported charsets:\n\ + utf8, ascii, wchar_t, ucs2be, ucs2le, utf16be, utf16le, ucs4be,\n\ + ucs4le, utf16, ucs4, ucs2, eucjp, shiftjis, iso2022jp, gb18030, gbk,\n\ + gb2312, big5, euckr, iso88591, latin1, iso88592, iso88593, iso88594,\n\ + iso88595, iso88596, iso88597, iso88598, iso88599, iso885910,\n\ + iso885911, iso885913, iso885914, iso885915, iso885916, cp1250,\n\ + windows1250, cp1251, windows1251, cp1252, windows1252, cp1253,\n\ + windows1253, cp1254, windows1254, cp1255, windows1255, cp1256,\n\ + windows1256, cp1257, windows1257, cp1258, windows1258, koi8r, koi8u,\n\ + cp437, cp850, cp866, ibm1047, cp1047.\n\ +\n\ +See also:\n\ + http://www.textfiles.com/art/\n\ + https://github.com/blocktronics/artpacks\n\ +\n" + +#define INBUFSZ 256 +#define OUBUFSZ (INBUFSZ * 6) +#define SLIT(s) ((unsigned)s[3] << 24 | s[2] << 16 | s[1] << 8 | s[0]) + +// "When new technology comes out, people don't all buy it right away. +// If what they have works, some will wait until it doesn't. A few +// people do get the latest though. In 1984 2400 baud modems became +// available, so some people had them, but many didn't. A BBS list +// from 1986 shows operators were mostly 300 and 1200, but some were +// using 2400. The next 5 years were the hayday of the 2400." +// +// https://forum.vcfed.org/index.php?threads/the-2400-baud-modem.44241/ + +int baud_rate = 2400; // -b 2400 +const char* from_charset = "CP437"; // -f CP437 +const char* to_charset = "UTF-8"; // -t UTF-8 + +volatile sig_atomic_t done; + +void on_signal(int sig) { + done = 1; + (void)sig; +} + +void print(const char* s) { + (void)!write(STDOUT_FILENO, s, strlen(s)); +} + +int encode_character(char output[8], const char* codec, wchar_t character) { + size_t inbytesleft = sizeof(wchar_t); + size_t outbytesleft = 7; + char* inbuf = (char*)&character; + char* outbuf = output; + iconv_t cd = iconv_open(codec, "wchar_t"); + if (cd == (iconv_t)-1) + return -1; + size_t result = iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft); + iconv_close(cd); + if (result == (size_t)-1) + return -1; + *outbuf = '\0'; + return 7 - outbytesleft; +} + +void append_replacement_character(char** b) { + int n = encode_character(*b, to_charset, 0xFFFD); + if (n == -1) + n = encode_character(*b, to_charset, '?'); + if (n != -1) + *b += n; +} + +int compare_time(struct timespec a, struct timespec b) { + int cmp; + if (!(cmp = (a.tv_sec > b.tv_sec) - (a.tv_sec < b.tv_sec))) + cmp = (a.tv_nsec > b.tv_nsec) - (a.tv_nsec < b.tv_nsec); + return cmp; +} + +struct timespec add_time(struct timespec x, struct timespec y) { + x.tv_sec += y.tv_sec; + x.tv_nsec += y.tv_nsec; + if (x.tv_nsec >= 1000000000) { + x.tv_nsec -= 1000000000; + x.tv_sec += 1; + } + return x; +} + +struct timespec subtract_time(struct timespec a, struct timespec b) { + a.tv_sec -= b.tv_sec; + if (a.tv_nsec < b.tv_nsec) { + a.tv_nsec += 1000000000; + a.tv_sec--; + } + a.tv_nsec -= b.tv_nsec; + return a; +} + +struct timespec fromnanos(long long x) { + struct timespec ts; + ts.tv_sec = x / 1000000000; + ts.tv_nsec = x % 1000000000; + return ts; +} + +void process_file(const char* path, int fd, iconv_t cd) { + size_t carry = 0; + struct timespec next; + char input_buffer[INBUFSZ]; + + clock_gettime(CLOCK_MONOTONIC, &next); + + for (;;) { + + // read from file + ssize_t bytes_read = read(fd, input_buffer + carry, INBUFSZ - carry); + if (!bytes_read) + return; + if (bytes_read == -1) { + perror(path); + done = 1; + return; + } + + // modernize character set + char* input_ptr = input_buffer; + size_t input_left = carry + bytes_read; + char output_buffer[OUBUFSZ]; + char* output_ptr = output_buffer; + size_t output_left = OUBUFSZ; + size_t ir = iconv(cd, &input_ptr, &input_left, &output_ptr, &output_left); + carry = 0; + if (ir == (size_t)-1) { + if (errno == EINVAL) { + // incomplete multibyte sequence encountered + memmove(input_buffer, input_ptr, input_left); + carry = input_left; + } else if (errno == EILSEQ && input_left) { + // EILSEQ means either + // 1. illegal input sequence encountered + // 2. code not encodable in output codec + // + // so we skip one byte of input, and insert � or ? in the output + // this isn't the most desirable behavior, but it is the best we + // can do, since we don't know specifics about the codecs in use + // + // unlike glibc cosmo's iconv implementation may handle case (2) + // automatically by inserting an asterisk in place of a sequence + ++input_ptr; + --input_left; + memmove(input_buffer, input_ptr, input_left); + carry = input_left; + if (output_left >= 8) + append_replacement_character(&output_ptr); + } else { + perror(path); + done = 1; + return; + } + } + + // write to terminal + for (char* p = output_buffer; p < output_ptr; p++) { + if (done) + return; + + (void)!write(STDOUT_FILENO, p, 1); + + // allow arrow keys to change baud rate + int have; + if (ioctl(STDIN_FILENO, FIONREAD, &have)) { + perror("ioctl"); + done = 1; + return; + } + if (have > 0) { + char key[4] = {0}; + if (read(STDIN_FILENO, key, sizeof(key)) > 0) { + if (SLIT(key) == SLIT("\33[A") || // up + SLIT(key) == SLIT("\33[C")) { // right + baud_rate *= 1.4; + } else if (SLIT(key) == SLIT("\33[B") || // down + SLIT(key) == SLIT("\33[D")) { // left + baud_rate *= 0.6; + } + if (baud_rate < 3) + baud_rate = 3; + if (baud_rate > 1000000000) + baud_rate = 1000000000; + } + } + + // insert artificial delay for one byte. we divide by 10 to convert + // bits to bytes, because that is how many bits 8-N-1 encoding used + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + next = add_time(next, fromnanos(1e9 / (baud_rate / 10.))); + if (compare_time(next, now) > 0) { + struct timespec sleep = subtract_time(next, now); + nanosleep(&sleep, 0); + } + } + } +} + +int main(int argc, char* argv[]) { + int opt; + while ((opt = getopt(argc, argv, "hb:f:t:")) != -1) { + switch (opt) { + case 'b': { + char* endptr; + double rate = strtod(optarg, &endptr); + if (*endptr == 'k') { + rate *= 1e3; + ++endptr; + } else if (*endptr == 'm') { + rate *= 1e6; + ++endptr; + } + if (*endptr || baud_rate <= 0) { + fprintf(stderr, "%s: invalid baud rate: %s\n", argv[0], optarg); + exit(1); + } + baud_rate = rate; + break; + } + case 'f': + from_charset = optarg; + break; + case 't': + to_charset = optarg; + break; + case 'h': + fprintf(stderr, HELP, baud_rate, from_charset, to_charset); + exit(0); + default: + fprintf(stderr, "protip: pass the -h flag for help\n"); + exit(1); + } + } + if (optind == argc) { + fprintf(stderr, "%s: missing operand\n", argv[0]); + exit(1); + } + + // create character transcoder + iconv_t cd = iconv_open(to_charset, from_charset); + if (cd == (iconv_t)-1) { + fprintf(stderr, "error: conversion from %s to %s not supported\n", + from_charset, to_charset); + exit(1); + } + + // catch ctrl-c + signal(SIGINT, on_signal); + + // don't wait until newline to read() keystrokes + struct termios t; + if (!tcgetattr(STDIN_FILENO, &t)) { + struct termios t2 = t; + t2.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &t2); + } + + // Process each file specified on the command line + for (int i = optind; i < argc && !done; i++) { + + // open file + int fd = open(argv[i], O_RDONLY); + if (fd == -1) { + perror(argv[i]); + break; + } + + // wait between files + if (i > optind) + sleep(1); + + print("\33[?25l"); // hide cursor + print("\33[H"); // move cursor to top-left + print("\33[J"); // erase display forward + print("\33[1;24r"); // set scrolling region to first 24 lines + print("\33[?7h"); // enable auto-wrap mode + print("\33[?3l"); // 80 column mode (deccolm) vt100 + print("\33[H"); // move cursor to top-left, again + + // get busy + process_file(argv[i], fd, cd); + close(fd); + } + + // cleanup + iconv_close(cd); + + print("\33[s"); // save cursor position + print("\33[?25h"); // show cursor + print("\33[0m"); // reset text attributes (color, bold, etc.) + print("\33[?1049l"); // exit alternate screen mode + print("\33(B"); // exit line drawing and other alt charset modes + print("\33[r"); // reset scrolling region + print("\33[?2004l"); // turn off bracketed paste mode + print("\33[4l"); // exit insert mode + print("\33[?1l\33>"); // exit application keypad mode + print("\33[?7h"); // reset text wrapping mode + print("\33[?12l"); // reset cursor blinking mode + print("\33[?6l"); // reset origin mode + print("\33[20l"); // reset auto newline mode + print("\33[u"); // restore cursor position + + // restore terminal + tcsetattr(STDIN_FILENO, TCSANOW, &t); +} diff --git a/examples/asteroids.c b/examples/asteroids.c new file mode 100644 index 000000000..d9537936c --- /dev/null +++ b/examples/asteroids.c @@ -0,0 +1,353 @@ +// -*- mode:c; indent-tabs-mode:nil; c-basic-offset:4 -*- +// vi: set et ft=c ts=4 sts=4 sw=4 fenc=utf-8 + +// asteroids by tsotchke +// https://github.com/tsotchke/asteroids + +// clang-format off + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SCREEN_WIDTH 80 +#define SCREEN_HEIGHT 24 +#define MAX_ASTEROIDS 5 +#define MAX_BULLETS 5 + +typedef struct { + float x, y; +} Vector2; + +typedef struct { + Vector2 position; + Vector2 velocity; + float angle; + float radius; +} GameObject; + +GameObject spaceship; +GameObject asteroids[MAX_ASTEROIDS]; +GameObject bullets[MAX_BULLETS]; + +int score = 0; +time_t startTime; +int isGameOver = 0; +int shouldExit = 0; +int finalTime = 0; // To store final time at game over +char display[SCREEN_HEIGHT][SCREEN_WIDTH]; + +// Function to clear the screen buffer +void clearDisplay() { + memset(display, ' ', sizeof(display)); +} + +// Function to draw a pixel on the screen +void drawPixel(int x, int y) { + if (x >= 0 && x < SCREEN_WIDTH && y >= 0 && y < SCREEN_HEIGHT) { + display[y][x] = '*'; + } +} + +// Function to draw a line using Bresenham's algorithm +void drawLine(int x1, int y1, int x2, int y2) { + int dx = abs(x2 - x1), sx = (x1 < x2) ? 1 : -1; + int dy = -abs(y2 - y1), sy = (y1 < y2) ? 1 : -1; + int error = dx + dy, e2; + + while (1) { + drawPixel(x1, y1); + if (x1 == x2 && y1 == y2) break; + e2 = 2 * error; + if (e2 >= dy) { error += dy; x1 += sx; } + if (e2 <= dx) { error += dx; y1 += sy; } + } +} + +// Function to draw a circle +void drawCircle(int centerX, int centerY, int radius) { + int x = radius - 1, y = 0, dx = 1, dy = 1, err = dx - (radius << 1); + while (x >= y) { + drawPixel(centerX + x, centerY + y); + drawPixel(centerX + y, centerY + x); + drawPixel(centerX - y, centerY + x); + drawPixel(centerX - x, centerY + y); + drawPixel(centerX - x, centerY - y); + drawPixel(centerX - y, centerY - x); + drawPixel(centerX + y, centerY - x); + drawPixel(centerX + x, centerY - y); + + if (err <= 0) { + y++; + err += dy; + dy += 2; + } + if (err > 0) { + x--; + dx += 2; + err += dx - (radius << 1); + } + } +} + +// Initialize a game object +void initializeGameObject(GameObject *obj, float x, float y, float angle, float radius) { + obj->position = (Vector2){x, y}; + obj->velocity = (Vector2){0, 0}; + obj->angle = angle; + obj->radius = radius; +} + +// Wrap position of the spaceship and asteroids within screen bounds +void wrapPosition(Vector2 *pos) { + if (pos->x < 0) pos->x = SCREEN_WIDTH - 1; + if (pos->x >= SCREEN_WIDTH) pos->x = 0; + if (pos->y < 0) pos->y = SCREEN_HEIGHT - 1; + if (pos->y >= SCREEN_HEIGHT) pos->y = 0; +} + +// Check if two game objects are colliding +int checkCollision(GameObject *a, GameObject *b) { + float deltaX = a->position.x - b->position.x; + float deltaY = a->position.y - b->position.y; + return sqrt(deltaX * deltaX + deltaY * deltaY) < (a->radius + b->radius); +} + +// Initialize game state +void initGame() { + score = 0; // Reset the score + initializeGameObject(&spaceship, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 0, 2); + + for (int i = 0; i < MAX_ASTEROIDS; i++) { + initializeGameObject(&asteroids[i], + rand() % SCREEN_WIDTH, + rand() % SCREEN_HEIGHT, + 0, + 2 + rand() % 3); + asteroids[i].velocity.x = ((float)rand() / RAND_MAX) * 2 - 1; + asteroids[i].velocity.y = ((float)rand() / RAND_MAX) * 2 - 1; + } + + for (int i = 0; i < MAX_BULLETS; i++) { + bullets[i].position.x = -1; // Mark bullet as inactive + bullets[i].position.y = -1; + } + + startTime = time(NULL); + isGameOver = 0; + finalTime = 0; // Reset final time +} + +// Draw the spaceship on the screen +void drawSpaceship() { + int x = (int)spaceship.position.x; + int y = (int)spaceship.position.y; + int size = 3; + + float cosAngle = cos(spaceship.angle); + float sinAngle = sin(spaceship.angle); + + int x1 = x + size * cosAngle; + int y1 = y + size * sinAngle; + int x2 = x + size * cos(spaceship.angle + 2.5); + int y2 = y + size * sin(spaceship.angle + 2.5); + int x3 = x + size * cos(spaceship.angle - 2.5); + int y3 = y + size * sin(spaceship.angle - 2.5); + + drawLine(x1, y1, x2, y2); + drawLine(x2, y2, x3, y3); + drawLine(x3, y3, x1, y1); +} + +// Draw all entities on the screen +void drawEntities(GameObject *entities, int count, void (*drawFunc)(GameObject *)) { + for (int i = 0; i < count; i++) { + drawFunc(&entities[i]); + } +} + +// Draw a bullet on the screen +void drawBullet(GameObject *bullet) { // Changed to non-const + if (bullet->position.x >= 0) { + drawPixel((int)bullet->position.x, (int)bullet->position.y); + } +} + +// Draw an asteroid on the screen +void drawAsteroid(GameObject *asteroid) { // Changed to non-const + drawCircle((int)asteroid->position.x, (int)asteroid->position.y, (int)asteroid->radius); +} + +// Refresh the display +void updateDisplay() { + clearDisplay(); + if (!isGameOver) { + drawSpaceship(); + drawEntities(asteroids, MAX_ASTEROIDS, drawAsteroid); + drawEntities(bullets, MAX_BULLETS, drawBullet); + } + + // Print the screen buffer + printf("\033[H"); + for (int y = 0; y < SCREEN_HEIGHT; y++) { + for (int x = 0; x < SCREEN_WIDTH; x++) { + putchar(display[y][x]); + } + putchar('\n'); + } + + // Display score and elapsed time + time_t currentTime = time(NULL); + int elapsedTime = isGameOver ? finalTime : (currentTime - startTime); + printf("Score: %d | Time: %02d:%02d | %s\n", score, elapsedTime / 60, elapsedTime % 60, isGameOver ? "Game Over!" : " "); +} + +// Update the position of game objects +void updateGameObject(GameObject *obj, int isBullet) { + obj->position.x += obj->velocity.x; + obj->position.y += obj->velocity.y; + + // If it's a bullet, check if it's out of bounds + if (isBullet) { + if (obj->position.x < 0 || obj->position.x >= SCREEN_WIDTH || obj->position.y < 0 || obj->position.y >= SCREEN_HEIGHT) { + obj->position.x = -1; // Deactivate bullet + obj->position.y = -1; + } + } else { + wrapPosition(&obj->position); + } +} + +// Update the game state +void updateGame() { + if (isGameOver) return; + + // Update spaceship and apply friction + updateGameObject(&spaceship, 0); // 0 indicates it's not a bullet + spaceship.velocity.x *= 0.98; + spaceship.velocity.y *= 0.98; + + // Move asteroids and check for collisions + for (int i = 0; i < MAX_ASTEROIDS; i++) { + updateGameObject(&asteroids[i], 0); + if (checkCollision(&spaceship, &asteroids[i])) { + isGameOver = 1; + finalTime = time(NULL) - startTime; + return; + } + } + + // Update bullet positions + for (int i = 0; i < MAX_BULLETS; i++) { + if (bullets[i].position.x >= 0) { + updateGameObject(&bullets[i], 1); // 1 indicates it's a bullet + } + } + + // Check for bullet collisions with asteroids + for (int i = 0; i < MAX_BULLETS; i++) { + if (bullets[i].position.x >= 0) { + for (int j = 0; j < MAX_ASTEROIDS; j++) { + if (checkCollision(&bullets[i], &asteroids[j])) { + bullets[i].position.x = -1; // Deactivate bullet + bullets[i].position.y = -1; + asteroids[j].position.x = rand() % SCREEN_WIDTH; + asteroids[j].position.y = rand() % SCREEN_HEIGHT; + score += 100; + } + } + } + } +} + +// Fire a bullet +void shootBullet() { + for (int i = 0; i < MAX_BULLETS; i++) { + if (bullets[i].position.x < 0) { + bullets[i].position = spaceship.position; + bullets[i].velocity.x = cos(spaceship.angle) * 2; + bullets[i].velocity.y = sin(spaceship.angle) * 2; + break; + } + } +} + +// Check if a key was hit +int isKeyHit() { + struct timeval tv = { 0L, 0L }; + fd_set fds; + FD_ZERO(&fds); + FD_SET(0, &fds); + return select(1, &fds, NULL, NULL, &tv); +} + +// Configure terminal settings +void configureTerminal(struct termios *old_tio, struct termios *new_tio) { + tcgetattr(STDIN_FILENO, old_tio); + *new_tio = *old_tio; + new_tio->c_lflag &= (~ICANON & ~ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, new_tio); +} + +// Restore terminal settings +void restoreTerminal(struct termios *old_tio) { + tcsetattr(STDIN_FILENO, TCSANOW, old_tio); +} + +void onSignal(int sig) { + shouldExit = 1; +} + +// Main game loop +int main() { + signal(SIGINT, onSignal); // Capture ^C + srand(time(NULL)); // Seed the random number generator + initGame(); // Initialize the game state + + struct termios old_tio, new_tio; + configureTerminal(&old_tio, &new_tio); + + printf("\033[?25l"); // Hide the cursor + + while (!shouldExit) { + if (isKeyHit()) { + char input = getchar(); + if (input == 27) { // ESC key + if (getchar() == '[') { // Handle arrow keys + switch (getchar()) { + case 'A': // Up arrow + spaceship.velocity.x += cos(spaceship.angle) * 0.2; + spaceship.velocity.y += sin(spaceship.angle) * 0.2; + break; + case 'B': // Down arrow + spaceship.velocity.x -= cos(spaceship.angle) * 0.2; + spaceship.velocity.y -= sin(spaceship.angle) * 0.2; + break; + case 'D': spaceship.angle -= 0.2; break; // Left arrow + case 'C': spaceship.angle += 0.2; break; // Right arrow + } + } + } else if (input == ' ') { + shootBullet(); // Fire a bullet + } else if (input == 'q') { + break; // Quit the game + } else if (input == 'r' && isGameOver) { + initGame(); // Restart the game + } + } + + updateGame(); // Update game state + updateDisplay(); // Refresh the display + usleep(50000); // Wait for 50ms (20 FPS) + } + + printf("\033[?25h"); // Show the cursor + restoreTerminal(&old_tio); // Restore terminal settings + return 0; +} diff --git a/examples/greenbean.c b/examples/greenbean.c index fda9ae999..eca939a7b 100644 --- a/examples/greenbean.c +++ b/examples/greenbean.c @@ -337,7 +337,7 @@ int main(int argc, char *argv[]) { sigaddset(&block, SIGQUIT); pthread_attr_t attr; unassert(!pthread_attr_init(&attr)); - unassert(!pthread_attr_setstacksize(&attr, 65536)); + unassert(!pthread_attr_setstacksize(&attr, 65536 - getpagesize())); unassert(!pthread_attr_setguardsize(&attr, getpagesize())); unassert(!pthread_attr_setsigmask_np(&attr, &block)); unassert(!pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0)); diff --git a/examples/nesemu1.cc b/examples/nesemu1.cc index 81118fe8e..a7597c265 100644 --- a/examples/nesemu1.cc +++ b/examples/nesemu1.cc @@ -13,7 +13,6 @@ #include "dsp/tty/tty.h" #include "libc/assert.h" #include "libc/calls/calls.h" -#include "libc/calls/struct/itimerval.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/struct/winsize.h" #include "libc/calls/termios.h" @@ -36,20 +35,17 @@ #include "libc/str/str.h" #include "libc/sysv/consts/ex.h" #include "libc/sysv/consts/exit.h" -#include "libc/sysv/consts/f.h" #include "libc/sysv/consts/fileno.h" -#include "libc/sysv/consts/itimer.h" -#include "libc/sysv/consts/o.h" #include "libc/sysv/consts/poll.h" #include "libc/sysv/consts/prio.h" #include "libc/sysv/consts/sig.h" -#include "libc/sysv/consts/w.h" #include "libc/thread/thread.h" #include "libc/time.h" #include "libc/x/xasprintf.h" #include "libc/x/xsigaction.h" #include "libc/zip.h" #include "third_party/getopt/getopt.internal.h" +#include "third_party/libcxx/__atomic/atomic.h" #include "third_party/libcxx/vector" #include "tool/viz/lib/knobs.h" @@ -124,15 +120,6 @@ typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; -static const struct itimerval kNesFps = { - {0, 1. / FPS * 1e6}, - {0, 1. / FPS * 1e6}, -}; - -struct Frame { - char *p, *w, *mem; -}; - struct Action { int code; int wait; @@ -148,15 +135,13 @@ struct ZipGames { char** p; }; -static int frame_; -static size_t vtsize_; +static const struct timespec kNesFps = {0, 1. / FPS * 1e9}; + static bool artifacts_; static long tyn_, txn_; -static struct Frame vf_[2]; static const char* inputfn_; static struct Status status_; static volatile bool exited_; -static volatile bool timeout_; static volatile bool resized_; static struct CosmoAudio* ca_; static struct TtyRgb* ttyrgb_; @@ -165,7 +150,7 @@ static struct ZipGames zipgames_; static struct Action arrow_, button_; static struct SamplingSolution* ssy_; static struct SamplingSolution* ssx_; -static unsigned char pixels_[3][DYN][DXN]; +static unsigned char (*pixels_)[3][DYN][DXN]; static unsigned char palette_[3][64][512][3]; static int joy_current_[2], joy_next_[2], joypos_[2]; @@ -173,6 +158,11 @@ static int keyframes_ = 10; static enum TtyBlocksSelection blocks_ = kTtyBlocksUnicode; static enum TtyQuantizationAlgorithm quant_ = kTtyQuantXterm256; +static struct timespec deadline_; +static std::atomic pixels_ready_; +static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + static int Clamp(int v) { return MAX(0, MIN(255, v)); } @@ -232,18 +222,8 @@ void InitPalette(void) { } } -static ssize_t Write(int fd, const void* p, size_t n) { - int rc; - sigset_t ss, oldss; - sigfillset(&ss); - sigprocmask(SIG_SETMASK, &ss, &oldss); - rc = write(fd, p, n); - sigprocmask(SIG_SETMASK, &oldss, 0); - return rc; -} - static void WriteString(const char* s) { - Write(STDOUT_FILENO, s, strlen(s)); + write(STDOUT_FILENO, s, strlen(s)); } void Exit(int rc) { @@ -264,28 +244,10 @@ void OnCtrlC(void) { exited_ = true; } -void OnTimer(void) { - timeout_ = true; -} - void OnResize(void) { resized_ = true; } -void OnPiped(void) { - exited_ = true; -} - -void OnSigChld(void) { - waitpid(-1, 0, WNOHANG); - cosmoaudio_close(ca_); - ca_ = 0; -} - -void InitFrame(struct Frame* f) { - f->p = f->w = f->mem = (char*)realloc(f->mem, vtsize_); -} - long ChopAxis(long dn, long sn) { while (HALF(sn) > dn) { sn = HALF(sn); @@ -308,11 +270,6 @@ void GetTermSize(void) { G = (unsigned char*)realloc(G, tyn_ * txn_); B = (unsigned char*)realloc(B, tyn_ * txn_); ttyrgb_ = (struct TtyRgb*)realloc(ttyrgb_, tyn_ * txn_ * 4); - vtsize_ = ((tyn_ * txn_ * strlen("\e[48;2;255;48;2;255m▄")) + - (tyn_ * strlen("\e[0m\r\n")) + 128); - frame_ = 0; - InitFrame(&vf_[0]); - InitFrame(&vf_[1]); WriteString("\e[0m\e[H\e[J"); resized_ = false; } @@ -320,11 +277,7 @@ void GetTermSize(void) { void IoInit(void) { GetTermSize(); xsigaction(SIGINT, (void*)OnCtrlC, 0, 0, NULL); - xsigaction(SIGPIPE, (void*)OnPiped, 0, 0, NULL); xsigaction(SIGWINCH, (void*)OnResize, 0, 0, NULL); - xsigaction(SIGALRM, (void*)OnTimer, 0, 0, NULL); - xsigaction(SIGCHLD, (void*)OnSigChld, 0, 0, NULL); - setitimer(ITIMER_REAL, &kNesFps, NULL); ttyhidecursor(STDOUT_FILENO); ttyraw(kTtySigs); ttyquantsetup(quant_, kTtyQuantRgb, blocks_); @@ -461,57 +414,29 @@ ssize_t ReadKeyboard(void) { return rc; } -bool HasVideo(struct Frame* f) { - return f->w < f->p; -} - -bool HasPendingVideo(void) { - return HasVideo(&vf_[0]) || HasVideo(&vf_[1]); -} - -struct Frame* FlipFrameBuffer(void) { - frame_ = !frame_; - return &vf_[frame_]; -} - -void TransmitVideo(void) { - ssize_t rc; - struct Frame* f; - f = &vf_[frame_]; - if (!HasVideo(f)) - f = FlipFrameBuffer(); - if ((rc = Write(STDOUT_FILENO, f->w, f->p - f->w)) != -1) { - f->w += rc; - } else if (errno == EAGAIN) { - // slow teletypewriter - } else if (errno == EPIPE) { - Exit(0); - } -} - -void ScaleVideoFrameToTeletypewriter(void) { +void ScaleVideoFrameToTeletypewriter(unsigned char (*pixels)[3][DYN][DXN]) { long y, x, yn, xn; yn = DYN, xn = DXN; while (HALF(yn) > tyn_ || HALF(xn) > txn_) { if (HALF(xn) > txn_) { - Magikarp2xX(DYN, DXN, pixels_[0], yn, xn); - Magikarp2xX(DYN, DXN, pixels_[1], yn, xn); - Magikarp2xX(DYN, DXN, pixels_[2], yn, xn); + Magikarp2xX(DYN, DXN, (*pixels)[0], yn, xn); + Magikarp2xX(DYN, DXN, (*pixels)[1], yn, xn); + Magikarp2xX(DYN, DXN, (*pixels)[2], yn, xn); xn = HALF(xn); } if (HALF(yn) > tyn_) { - Magikarp2xY(DYN, DXN, pixels_[0], yn, xn); - Magikarp2xY(DYN, DXN, pixels_[1], yn, xn); - Magikarp2xY(DYN, DXN, pixels_[2], yn, xn); + Magikarp2xY(DYN, DXN, (*pixels)[0], yn, xn); + Magikarp2xY(DYN, DXN, (*pixels)[1], yn, xn); + Magikarp2xY(DYN, DXN, (*pixels)[2], yn, xn); yn = HALF(yn); } } - GyaradosUint8(tyn_, txn_, R, DYN, DXN, pixels_[0], tyn_, txn_, yn, xn, 0, 255, - ssy_, ssx_, true); - GyaradosUint8(tyn_, txn_, G, DYN, DXN, pixels_[1], tyn_, txn_, yn, xn, 0, 255, - ssy_, ssx_, true); - GyaradosUint8(tyn_, txn_, B, DYN, DXN, pixels_[2], tyn_, txn_, yn, xn, 0, 255, - ssy_, ssx_, true); + GyaradosUint8(tyn_, txn_, R, DYN, DXN, (*pixels)[0], tyn_, txn_, yn, xn, 0, + 255, ssy_, ssx_, true); + GyaradosUint8(tyn_, txn_, G, DYN, DXN, (*pixels)[1], tyn_, txn_, yn, xn, 0, + 255, ssy_, ssx_, true); + GyaradosUint8(tyn_, txn_, B, DYN, DXN, (*pixels)[2], tyn_, txn_, yn, xn, 0, + 255, ssy_, ssx_, true); for (y = 0; y < tyn_; ++y) { for (x = 0; x < txn_; ++x) { ttyrgb_[y * txn_ + x] = @@ -528,55 +453,104 @@ void KeyCountdown(struct Action* a) { } } -void PollAndSynchronize(void) { - do { - if (ReadKeyboard() == -1) { - if (errno != EINTR) - Exit(1); - if (exited_) - Exit(0); - if (resized_) - GetTermSize(); - } - } while (!timeout_); - TransmitVideo(); - timeout_ = false; - KeyCountdown(&arrow_); - KeyCountdown(&button_); - joy_next_[0] = arrow_.code | button_.code; - joy_next_[1] = arrow_.code | button_.code; -} - -void Raster(void) { - struct Frame* f; +void Raster(unsigned char (*pixels)[3][DYN][DXN]) { struct TtyRgb bg = {0x12, 0x34, 0x56, 0}; struct TtyRgb fg = {0x12, 0x34, 0x56, 0}; - ScaleVideoFrameToTeletypewriter(); - f = &vf_[!frame_]; - f->p = f->w = f->mem; - f->p = stpcpy(f->p, "\e[0m\e[H"); - f->p = ttyraster(f->p, ttyrgb_, tyn_, txn_, bg, fg); + ScaleVideoFrameToTeletypewriter(pixels); + char* ansi = (char*)malloc((tyn_ * txn_ * strlen("\e[48;2;255;48;2;255m▄")) + + (tyn_ * strlen("\e[0m\r\n")) + 128); + char* p = ansi; + p = stpcpy(p, "\e[0m\e[H"); + p = ttyraster(p, ttyrgb_, tyn_, txn_, bg, fg); + free(pixels); if (status_.wait) { status_.wait--; - f->p = stpcpy(f->p, "\e[0m\e[H"); - f->p = stpcpy(f->p, status_.text); + p = stpcpy(p, "\e[0m\e[H"); + p = stpcpy(p, status_.text); + } + size_t n = p - ansi; + ssize_t wrote; + for (size_t i = 0; i < n; i += wrote) { + if ((wrote = write(STDOUT_FILENO, ansi + i, n - i)) == -1) { + exited_ = true; + break; + } + } + free(ansi); +} + +void* RasterThread(void* arg) { + sigset_t ss; + sigemptyset(&ss); + sigaddset(&ss, SIGINT); + sigaddset(&ss, SIGHUP); + sigaddset(&ss, SIGQUIT); + sigaddset(&ss, SIGTERM); + sigaddset(&ss, SIGPIPE); + sigprocmask(SIG_SETMASK, &ss, 0); + for (;;) { + unsigned char(*pixels)[3][DYN][DXN]; + pthread_mutex_lock(&lock); + while (!(pixels = (unsigned char(*)[3][DYN][DXN])pixels_ready_.load())) + pthread_cond_wait(&cond, &lock); + pixels_ready_.store(0); + pthread_mutex_unlock(&lock); + if (resized_) + GetTermSize(); + Raster(pixels); } - PollAndSynchronize(); } void FlushScanline(unsigned py) { - if (py == DYN - 1) { - if (!timeout_) - Raster(); - timeout_ = false; + if (py != DYN - 1) + return; + pthread_mutex_lock(&lock); + if (!pixels_ready_) { + pixels_ready_.store(pixels_); + pixels_ = 0; + pthread_cond_signal(&cond); } + pthread_mutex_unlock(&lock); + if (!pixels_) + pixels_ = (unsigned char(*)[3][DYN][DXN])malloc(3 * DYN * DXN); + if (exited_) + Exit(0); + do { + struct timespec now = timespec_mono(); + struct timespec remain = timespec_subz(deadline_, now); + int remain_ms = timespec_tomillis(remain); + struct pollfd fds[] = {{STDIN_FILENO, POLLIN}}; + int got = poll(fds, 1, remain_ms); + if (got == -1) { + if (errno == EINTR) + continue; + Exit(1); + } + if (got == 1) { + do { + if (ReadKeyboard() == -1) { + if (errno == EINTR) + continue; + Exit(1); + } + } while (0); + } + KeyCountdown(&arrow_); + KeyCountdown(&button_); + joy_next_[0] = arrow_.code | button_.code; + joy_next_[1] = arrow_.code | button_.code; + now = timespec_mono(); + do + deadline_ = timespec_add(deadline_, kNesFps); + while (timespec_cmp(deadline_, now) <= 0); + } while (0); } static void PutPixel(unsigned px, unsigned py, unsigned pixel, int offset) { static unsigned prev; - pixels_[0][py][px] = palette_[offset][prev % 64][pixel][2]; - pixels_[1][py][px] = palette_[offset][prev % 64][pixel][1]; - pixels_[2][py][px] = palette_[offset][prev % 64][pixel][0]; + (*pixels_)[0][py][px] = palette_[offset][prev % 64][pixel][2]; + (*pixels_)[1][py][px] = palette_[offset][prev % 64][pixel][1]; + (*pixels_)[2][py][px] = palette_[offset][prev % 64][pixel][0]; prev = pixel; } @@ -1732,8 +1706,18 @@ int PlayGame(const char* romfile, const char* opt_tasfile) { return 3; } + // initialize screen + pixels_ = (unsigned char(*)[3][DYN][DXN])malloc(3 * DYN * DXN); InitPalette(); + // start raster thread + errno_t err; + pthread_t th; + if ((err = pthread_create(&th, 0, RasterThread, 0))) { + fprintf(stderr, "pthread_create: %s\n", strerror(err)); + exit(1); + } + // open speaker struct CosmoAudioOpenOptions cao = {}; cao.sizeofThis = sizeof(struct CosmoAudioOpenOptions); @@ -1742,6 +1726,9 @@ int PlayGame(const char* romfile, const char* opt_tasfile) { cao.channels = 1; cosmoaudio_open(&ca_, &cao); + // initialize time + deadline_ = timespec_add(timespec_mono(), kNesFps); + // Read the ROM file header u8 rom16count = fgetc(fp); u8 vrom8count = fgetc(fp); diff --git a/examples/rote.c b/examples/rote.c new file mode 100644 index 000000000..3819ce331 --- /dev/null +++ b/examples/rote.c @@ -0,0 +1,322 @@ +#/*────────────────────────────────────────────────────────────────╗ +┌┘ 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/ │ +╚─────────────────────────────────────────────────────────────────*/ +#include +#include +#include +#include +#include +#include +#include + +/** + * @fileoverview cosmopolitan flash cards viewer + */ + +struct Card { + char* qa[2]; +}; + +atomic_int g_done; + +void onsig(int sig) { + g_done = 1; +} + +void* xmalloc(int n) { + void* p; + if ((p = malloc(n))) + return p; + perror("malloc"); + exit(1); +} + +void* xrealloc(void* p, int n) { + if ((p = realloc(p, n))) + return p; + perror("realloc"); + exit(1); +} + +char* xstrcat(const char* a, const char* b) { + char* p; + size_t n, m; + n = strlen(a); + m = strlen(b); + p = xmalloc(n + m + 1); + memcpy(p, a, n); + memcpy(p + n, b, m + 1); + return p; +} + +void shuffle(struct Card* a, int n) { + while (n > 1) { + int i = rand() % n--; + struct Card t = a[i]; + a[i] = a[n]; + a[n] = t; + } +} + +char* trim(char* s) { + int i; + if (s) { + while (isspace(*s)) + ++s; + for (i = strlen(s); i--;) { + if (isspace(s[i])) { + s[i] = 0; + } else { + break; + } + } + } + return s; +} + +char* readline(FILE* f) { + for (;;) { + char* line = trim(fgetln(f, 0)); + if (!line) + return 0; + if (*line != '#') + if (*line) + return line; + } +} + +char* fill(const char* text, int max_line_width, int* out_line_count) { + int text_len = strlen(text); + char* result = xmalloc(text_len * 2 + 1); + int result_pos = 0; + int line_start = 0; + int line_count = 1; + int i = 0; + while (i < text_len && isspace(text[i])) + i++; + while (i < text_len) { + int word_end = i; + while (word_end < text_len && !isspace(text[word_end])) + word_end++; + int word_length = word_end - i; + if ((result_pos - line_start) + (result_pos > line_start ? 1 : 0) + + word_length > + max_line_width) { + if (result_pos > line_start) { + ++line_count; + result[result_pos++] = '\n'; + line_start = result_pos; + } + } else if (result_pos > line_start) { + result[result_pos++] = ' '; + } + memcpy(result + result_pos, text + i, word_length); + result_pos += word_length; + i = word_end; + while (i < text_len && isspace(text[i])) + i++; + } + result[result_pos] = '\0'; + result = xrealloc(result, result_pos + 1); + if (out_line_count) + *out_line_count = line_count; + return result; +} + +void show(const char* text, int i, int n) { + + // get pseudoteletypewriter dimensions + struct winsize ws = {80, 25}; + tcgetwinsize(1, &ws); + int width = ws.ws_col; + if (width > (int)(ws.ws_col * .9)) + width = ws.ws_col * .9; + if (width > 80) + width = 80; + width &= -2; + + // clear display + printf("\033[H\033[J"); + + // display flash card text in middle of display + char buf[32]; + int line_count; + char* lines = fill(text, width, &line_count); + sprintf(buf, "%d/%d\r\n\r\n", i + 1, n); + line_count += 2; + char* extra = xstrcat(buf, lines); + free(lines); + char* tokens = extra; + for (int j = 0;; ++j) { + char* line = strtok(tokens, "\n"); + tokens = 0; + if (!line) + break; + printf("\033[%d;%dH%s", ws.ws_row / 2 - line_count / 2 + j + 1, + ws.ws_col / 2 - strlen(line) / 2 + 1, line); + } + free(extra); + fflush(stdout); +} + +void usage(FILE* f, const char* prog) { + fprintf(f, + "usage: %s FILE\n" + "\n" + "here's an example of what your file should look like:\n" + "\n" + " # cosmopolitan flash cards\n" + " # california dmv drivers test\n" + " \n" + " which of the following point totals could result in " + "your license being suspended by the dmv?\n" + " 4 points in 12 months (middle)\n" + " \n" + " at 55 mph under good conditions a passenger vehicle can stop " + "within\n" + " 300 feet (not 200, not 400, middle)\n" + " \n" + " two sets of solid double yellow lines spaced two or more feet " + "apart indicate\n" + " a BARRIER (do not cross unless there's an opening)\n" + "\n" + "more specifically, empty lines are ignored, lines starting with\n" + "a hash are ignored, then an even number of lines must remain,\n" + "where each two lines is a card, holding question and answer.\n", + prog); +} + +int main(int argc, char* argv[]) { + + // show help + if (argc != 2) { + usage(stderr, argv[0]); + return 1; + } + if (!strcmp(argv[1], "-?") || // + !strcmp(argv[1], "-h") || // + !strcmp(argv[1], "--help")) { + usage(stdout, argv[0]); + return 0; + } + + // teletypewriter is required + if (!isatty(0) || !isatty(1)) { + perror("isatty"); + return 2; + } + + // load cards + FILE* f = fopen(argv[1], "r"); + if (!f) { + perror(argv[1]); + return 3; + } + int count = 0; + struct Card* cards = 0; + for (;;) { + struct Card card; + if (!(card.qa[0] = readline(f))) + break; + card.qa[0] = strdup(card.qa[0]); + if (!(card.qa[1] = readline(f))) { + fprintf(stderr, "%s: flash card file has odd number of lines\n", argv[1]); + exit(1); + } + card.qa[1] = strdup(card.qa[1]); + cards = xrealloc(cards, (count + 1) * sizeof(struct Card)); + cards[count++] = card; + } + fclose(f); + + // randomize + srand(time(0)); + shuffle(cards, count); + + // catch ctrl-c + struct sigaction sa; + sa.sa_flags = 0; + sa.sa_handler = onsig; + sigemptyset(&sa.sa_mask); + sigaction(SIGINT, &sa, 0); + + // enter raw mode + struct termios ot; + tcgetattr(1, &ot); + struct termios nt = ot; + cfmakeraw(&nt); + nt.c_lflag |= ISIG; + tcsetattr(1, TCSANOW, &nt); + printf("\033[?25l"); + + // show flash cards + int i = 0; + while (!g_done) { + show(cards[i / 2].qa[i % 2], i / 2, count); + + // press any key + char b[8] = {0}; + read(0, b, sizeof(b)); + + // q quits + if (b[0] == 'q') + break; + + // b or ctrl-b goes backward + if (b[0] == 'b' || // + b[0] == ('B' ^ 0100)) { + if (--i < 0) + i = count * 2 - 1; + i &= -2; + continue; + } + + // p or ctrl-p goes backward + if (b[0] == 'p' || // + b[0] == ('P' ^ 0100)) { + if (--i < 0) + i = count * 2 - 1; + i &= -2; + continue; + } + + // up arrow goes backward + if (b[0] == 033 && // + b[1] == '[' && // + b[2] == 'A') { + if (--i < 0) + i = count * 2 - 1; + i &= -2; + continue; + } + + // left arrow goes backward + if (b[0] == 033 && // + b[1] == '[' && // + b[2] == 'D') { + if (--i < 0) + i = count * 2 - 1; + i &= -2; + continue; + } + + // only advance + if (++i == count * 2) + i = 0; + } + + // free memory + for (int i = 0; i < count; ++i) + for (int j = 0; j < 2; ++j) + free(cards[i].qa[j]); + free(cards); + + // cleanup terminal and show cursor + tcsetattr(1, TCSANOW, &ot); + printf("\033[?25h"); + printf("\n"); +} diff --git a/examples/script.c b/examples/script.c index 8068a75d5..414f07e8c 100644 --- a/examples/script.c +++ b/examples/script.c @@ -49,10 +49,10 @@ * @fileoverview Terminal Screencast Recorder / Player, e.g. * * make o//examples/script.com - * o//examples/script.com -r + * o//examples/script.com -w80 -h24 -r recording.tty * # type stuff.. * # CTRL-D - * o//examples/script.com -p typescript + * o//examples/script.com -p recording.tty * * @note works on Linux, OpenBSD, NetBSD, FreeBSD, MacOS * @see https://asciinema.org/ @@ -103,9 +103,9 @@ main(int argc, char *argv[]) fd_set rfd; int fm_fd; int aflg, Fflg, kflg, pflg, ch, k, n; - int flushtime, readstdin; + int flushtime, readstdin, width, height; - aflg = Fflg = kflg = pflg = 0; + aflg = Fflg = kflg = pflg = height = width = 0; usesleep = 1; rawout = 0; flushtime = 30; @@ -115,7 +115,7 @@ main(int argc, char *argv[]) (void)fm_fd; - while ((ch = getopt(argc, argv, "adeFfkpqrt:")) != -1) + while ((ch = getopt(argc, argv, "adeFfkpqrt:w:h:")) != -1) switch(ch) { case 'a': aflg = 1; @@ -145,6 +145,12 @@ main(int argc, char *argv[]) if (flushtime < 0) err(1, "invalid flush time %d", flushtime); break; + case 'w': + width = atoi(optarg); + break; + case 'h': + height = atoi(optarg); + break; case '?': default: usage(); @@ -172,6 +178,10 @@ main(int argc, char *argv[]) if (openpty(&master, &slave, NULL, NULL, NULL) == -1) err(1, "openpty"); } else { + if (width) + win.ws_col = width; + if (height) + win.ws_row = height; if (openpty(&master, &slave, NULL, &tt, &win) == -1) err(1, "openpty"); ttyflg = 1; diff --git a/examples/stackexplorer.c b/examples/stackexplorer.c index 5b5d9add9..96c34114c 100644 --- a/examples/stackexplorer.c +++ b/examples/stackexplorer.c @@ -7,9 +7,13 @@ │ • http://creativecommons.org/publicdomain/zero/1.0/ │ ╚─────────────────────────────────────────────────────────────────*/ #endif +#include "libc/dce.h" +#include "libc/intrin/maps.h" #include "libc/mem/alg.h" #include "libc/mem/mem.h" #include "libc/runtime/runtime.h" +#include "libc/runtime/stack.h" +#include "libc/runtime/winargs.internal.h" #include "libc/stdio/stdio.h" #include "libc/x/xasprintf.h" @@ -67,8 +71,18 @@ int main(int argc, char *argv[]) { Append((uintptr_t)&__auxv[i + 1], xasprintf("&auxv[%d] = %#lx", i + 1, __auxv[i + 1])); } - qsort(things.p, things.n, sizeof(*things.p), Compare); - for (int i = 0; i < things.n; ++i) { - printf("%012lx %s\n", things.p[i].i, things.p[i].s); + if (!IsWindows()) { + struct AddrSize stak = __get_main_stack(); + Append((intptr_t)stak.addr + stak.size, "top of stack"); + Append((intptr_t)stak.addr, "bottom of stack"); + } else { +#ifdef __x86_64__ + Append(GetStaticStackAddr(0) + GetStaticStackSize(), "top of stack"); + Append(GetStaticStackAddr(0) + GetGuardSize(), "bottom of stack"); + Append(GetStaticStackAddr(0), "bottom of guard region"); +#endif } + qsort(things.p, things.n, sizeof(*things.p), Compare); + for (int i = 0; i < things.n; ++i) + printf("%012lx %s\n", things.p[i].i, things.p[i].s); } diff --git a/examples/thread.c b/examples/thread.c new file mode 100644 index 000000000..283c2f8b0 --- /dev/null +++ b/examples/thread.c @@ -0,0 +1,17 @@ +#include +#include + +// how to spawn a thread + +void *my_thread(void *arg) { + printf("my_thread(%p) is running\n", arg); + return (void *)0x456L; +} + +int main(int argc, char *argv[]) { + void *res; + pthread_t th; + pthread_create(&th, 0, my_thread, (void *)0x123L); + pthread_join(th, &res); + printf("my_thread() returned %p\n", res); +} diff --git a/libc/calls/BUILD.mk b/libc/calls/BUILD.mk index ea3b8b75d..75ac5a00a 100644 --- a/libc/calls/BUILD.mk +++ b/libc/calls/BUILD.mk @@ -216,12 +216,6 @@ o//libc/calls/writev.o: private \ -mgeneral-regs-only # these assembly files are safe to build on aarch64 -o/$(MODE)/libc/calls/getcontext.o: libc/calls/getcontext.S - @$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -c $< -o/$(MODE)/libc/calls/swapcontext.o: libc/calls/swapcontext.S - @$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -c $< -o/$(MODE)/libc/calls/tailcontext.o: libc/calls/tailcontext.S - @$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -c $< o/$(MODE)/libc/calls/stackjump.o: libc/calls/stackjump.S @$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -c $< diff --git a/libc/calls/clock_nanosleep-nt.c b/libc/calls/clock_nanosleep-nt.c index 1546447a9..1e1a09cbd 100644 --- a/libc/calls/clock_nanosleep-nt.c +++ b/libc/calls/clock_nanosleep-nt.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/atomic.h" #include "libc/calls/internal.h" #include "libc/calls/struct/sigset.internal.h" #include "libc/calls/struct/timespec.h" @@ -23,26 +24,37 @@ #include "libc/calls/syscall-sysv.internal.h" #include "libc/errno.h" #include "libc/intrin/atomic.h" +#include "libc/nt/enum/status.h" +#include "libc/nt/ntdll.h" #include "libc/stdio/sysparam.h" +#include "libc/sysv/consts/clock.h" #include "libc/sysv/consts/timer.h" #include "libc/thread/tls.h" #ifdef __x86_64__ +static atomic_int usingRes; +static atomic_bool changedRes; + static textwindows int sys_clock_nanosleep_nt_impl(int clock, struct timespec abs, sigset_t waitmask) { - uint32_t msdelay; - struct timespec now; - for (;;) { - if (sys_clock_gettime_nt(clock, &now)) - return -1; - if (timespec_cmp(now, abs) >= 0) - return 0; - msdelay = timespec_tomillis(timespec_sub(abs, now)); - msdelay = MIN(msdelay, -1u); - if (_park_norestart(msdelay, waitmask)) - return -1; - } + struct timespec now, wall; + uint32_t minRes, maxRes, oldRes; + sys_clock_gettime_nt(0, &wall); + if (sys_clock_gettime_nt(clock, &now)) + return -1; + bool wantRes = clock == CLOCK_REALTIME || // + clock == CLOCK_MONOTONIC || // + clock == CLOCK_BOOTTIME; + if (wantRes && !atomic_fetch_add(&usingRes, 1)) + changedRes = NtSuccess(NtQueryTimerResolution(&minRes, &maxRes, &oldRes)) && + NtSuccess(NtSetTimerResolution(maxRes, true, &oldRes)); + if (timespec_cmp(abs, now) > 0) + wall = timespec_add(wall, timespec_sub(abs, now)); + int rc = _park_norestart(wall, waitmask); + if (wantRes && atomic_fetch_sub(&usingRes, 1) == 1 && changedRes) + NtSetTimerResolution(0, false, &minRes); + return rc; } textwindows int sys_clock_nanosleep_nt(int clock, int flags, diff --git a/libc/calls/clock_nanosleep-openbsd.c b/libc/calls/clock_nanosleep-openbsd.c index 25718feb2..dec285314 100644 --- a/libc/calls/clock_nanosleep-openbsd.c +++ b/libc/calls/clock_nanosleep-openbsd.c @@ -23,9 +23,9 @@ #include "libc/sysv/consts/clock.h" #include "libc/sysv/errfuns.h" -int sys_clock_nanosleep_openbsd(int clock, int flags, - const struct timespec *req, - struct timespec *rem) { +relegated int sys_clock_nanosleep_openbsd(int clock, int flags, + const struct timespec *req, + struct timespec *rem) { int res; struct timespec start, relative, remainder; if (!flags) { diff --git a/libc/calls/clock_nanosleep.c b/libc/calls/clock_nanosleep.c index 5415373a5..459e50328 100644 --- a/libc/calls/clock_nanosleep.c +++ b/libc/calls/clock_nanosleep.c @@ -57,6 +57,7 @@ * * @param clock may be * - `CLOCK_REALTIME` + * - `CLOCK_BOOTTIME` * - `CLOCK_MONOTONIC` * - `CLOCK_REALTIME_COARSE` but is likely to sleep negative time * - `CLOCK_MONTONIC_COARSE` but is likely to sleep negative time diff --git a/libc/calls/copy_file_range.c b/libc/calls/copy_file_range.c index af90ab52d..1a340710e 100644 --- a/libc/calls/copy_file_range.c +++ b/libc/calls/copy_file_range.c @@ -83,9 +83,11 @@ static void copy_file_range_init(void) { * @return number of bytes transferred, or -1 w/ errno * @raise EXDEV if source and destination are on different filesystems * @raise EBADF if `infd` or `outfd` aren't open files or append-only + * @raise EOPNOTSUPP if filesystem doesn't support this operation * @raise EPERM if `fdout` refers to an immutable file on Linux * @raise ECANCELED if thread was cancelled in masked mode * @raise EINVAL if ranges overlap or `flags` is non-zero + * @raise EINVAL on eCryptFs filesystems that have a bug * @raise EFBIG if `setrlimit(RLIMIT_FSIZE)` is exceeded * @raise EFAULT if one of the pointers memory is bad * @raise ERANGE if overflow happens computing ranges diff --git a/libc/calls/createpipename.c b/libc/calls/createpipename.c index a5d518522..f8eed1531 100644 --- a/libc/calls/createpipename.c +++ b/libc/calls/createpipename.c @@ -17,26 +17,10 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/atomic.h" +#include "libc/fmt/internal.h" #include "libc/intrin/atomic.h" #include "libc/runtime/internal.h" -static textwindows char16_t *itoa16(char16_t p[21], uint64_t x) { - char t; - size_t a, b, i = 0; - do { - p[i++] = x % 10 + '0'; - x = x / 10; - } while (x > 0); - if (i) { - for (a = 0, b = i - 1; a < b; ++a, --b) { - t = p[a]; - p[a] = p[b]; - p[b] = t; - } - } - return p + i; -} - // This function is called very early by WinMain(). textwindows char16_t *__create_pipe_name(char16_t *a) { char16_t *p = a; @@ -44,9 +28,9 @@ textwindows char16_t *__create_pipe_name(char16_t *a) { static atomic_uint x; while (*q) *p++ = *q++; - p = itoa16(p, __pid); + p = __itoa16(p, __pid); *p++ = '-'; - p = itoa16(p, atomic_fetch_add(&x, 1)); + p = __itoa16(p, atomic_fetch_add(&x, 1)); *p = 0; return a; } diff --git a/libc/calls/fcntl-nt.c b/libc/calls/fcntl-nt.c index a10b585f3..77b8331a1 100644 --- a/libc/calls/fcntl-nt.c +++ b/libc/calls/fcntl-nt.c @@ -51,6 +51,7 @@ #include "libc/sysv/consts/fio.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/errfuns.h" +#include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" struct FileLock { @@ -67,7 +68,9 @@ struct FileLocks { struct FileLock *free; }; -static struct FileLocks g_locks; +static struct FileLocks g_locks = { + .mu = PTHREAD_MUTEX_INITIALIZER, +}; static textwindows struct FileLock *NewFileLock(void) { struct FileLock *fl; @@ -110,7 +113,7 @@ static textwindows bool EqualsFileLock(struct FileLock *fl, int64_t off, textwindows void sys_fcntl_nt_lock_cleanup(int fd) { struct FileLock *fl, *ft, **flp; - pthread_mutex_lock(&g_locks.mu); + _pthread_mutex_lock(&g_locks.mu); for (flp = &g_locks.list, fl = *flp; fl;) { if (fl->fd == fd) { *flp = fl->next; @@ -122,7 +125,7 @@ textwindows void sys_fcntl_nt_lock_cleanup(int fd) { fl = *flp; } } - pthread_mutex_unlock(&g_locks.mu); + _pthread_mutex_unlock(&g_locks.mu); } static textwindows int64_t GetfileSize(int64_t handle) { @@ -353,9 +356,9 @@ textwindows int sys_fcntl_nt(int fd, int cmd, uintptr_t arg) { } else if (cmd == F_SETLK || cmd == F_SETLKW || cmd == F_GETLK) { struct Fd *f = g_fds.p + fd; if (f->cursor) { - pthread_mutex_lock(&g_locks.mu); + _pthread_mutex_lock(&g_locks.mu); rc = sys_fcntl_nt_lock(f, fd, cmd, arg); - pthread_mutex_unlock(&g_locks.mu); + _pthread_mutex_unlock(&g_locks.mu); } else { rc = ebadf(); } diff --git a/libc/calls/fstat-nt.c b/libc/calls/fstat-nt.c index 97f77eae7..b3edea321 100644 --- a/libc/calls/fstat-nt.c +++ b/libc/calls/fstat-nt.c @@ -122,34 +122,29 @@ textwindows int sys_fstat_nt_handle(int64_t handle, const char16_t *path, st.st_blksize = 4096; st.st_gid = st.st_uid = sys_getuid_nt(); - // We'll use the "umask" to fake out the mode bits. - uint32_t umask = atomic_load_explicit(&__umask, memory_order_acquire); - switch (GetFileType(handle)) { case kNtFileTypeUnknown: break; case kNtFileTypeChar: - st.st_mode = S_IFCHR | (0666 & ~umask); + st.st_mode = S_IFCHR | 0664; st.st_dev = 0x66666666; st.st_ino = handle; break; case kNtFileTypePipe: - st.st_mode = S_IFIFO | (0666 & ~umask); + st.st_mode = S_IFIFO | 0664; st.st_dev = 0x55555555; st.st_ino = handle; break; case kNtFileTypeDisk: { struct NtByHandleFileInformation wst; if (GetFileInformationByHandle(handle, &wst)) { - st.st_mode = 0444 & ~umask; + st.st_mode = 0444; if ((wst.dwFileAttributes & kNtFileAttributeDirectory) || - IsWindowsExecutable(handle, path)) { - st.st_mode |= 0111 & ~umask; - } + IsWindowsExecutable(handle, path)) + st.st_mode |= 0111; st.st_flags = wst.dwFileAttributes; - if (!(wst.dwFileAttributes & kNtFileAttributeReadonly)) { - st.st_mode |= 0222 & ~umask; - } + if (!(wst.dwFileAttributes & kNtFileAttributeReadonly)) + st.st_mode |= 0220; if (wst.dwFileAttributes & kNtFileAttributeReparsePoint) { st.st_mode |= S_IFLNK; } else if (wst.dwFileAttributes & kNtFileAttributeDirectory) { @@ -195,7 +190,7 @@ textwindows int sys_fstat_nt_handle(int64_t handle, const char16_t *path, } else if (GetVolumeInformationByHandle( handle, 0, 0, &wst.dwVolumeSerialNumber, 0, 0, 0, 0)) { st.st_dev = wst.dwVolumeSerialNumber; - st.st_mode = S_IFDIR | (0444 & ~umask); + st.st_mode = S_IFDIR | 0555; } else { // both GetFileInformationByHandle and // GetVolumeInformationByHandle failed diff --git a/libc/calls/getgroups.c b/libc/calls/getgroups.c index d4c8fe1a9..25251453b 100644 --- a/libc/calls/getgroups.c +++ b/libc/calls/getgroups.c @@ -21,7 +21,6 @@ #include "libc/dce.h" #include "libc/intrin/describeflags.h" #include "libc/intrin/strace.h" -#include "libc/stdckdint.h" #include "libc/sysv/errfuns.h" /** diff --git a/libc/calls/getloadavg-nt.c b/libc/calls/getloadavg-nt.c index 08d733536..77e0a83ed 100644 --- a/libc/calls/getloadavg-nt.c +++ b/libc/calls/getloadavg-nt.c @@ -21,24 +21,23 @@ #include "libc/calls/syscall_support-nt.internal.h" #include "libc/dce.h" #include "libc/fmt/conv.h" +#include "libc/intrin/cxaatexit.h" #include "libc/macros.h" #include "libc/nt/accounting.h" #include "libc/runtime/runtime.h" -#include "libc/thread/thread.h" +#define CTOR __attribute__((__constructor__(99))) #define FT(x) (x.dwLowDateTime | (uint64_t)x.dwHighDateTime << 32) static int cpus; static double load; -static pthread_spinlock_t lock; static struct NtFileTime idle1, kern1, user1; textwindows int sys_getloadavg_nt(double *a, int n) { int i, rc; uint64_t elapsed, used; struct NtFileTime idle, kern, user; - BLOCK_SIGNALS; - pthread_spin_lock(&lock); + __cxa_lock(); if (GetSystemTimes(&idle, &kern, &user)) { elapsed = (FT(kern) - FT(kern1)) + (FT(user) - FT(user1)); if (elapsed) { @@ -54,12 +53,11 @@ textwindows int sys_getloadavg_nt(double *a, int n) { } else { rc = __winerr(); } - pthread_spin_unlock(&lock); - ALLOW_SIGNALS; + __cxa_unlock(); return rc; } -__attribute__((__constructor__(40))) static textstartup void ntinitload(void) { +CTOR static textstartup void sys_getloadavg_nt_init(void) { if (IsWindows()) { load = 1; cpus = __get_cpu_count() / 2; diff --git a/libc/calls/getntsyspath.S b/libc/calls/getntsyspath.S index b8f2b65cd..bd8178bd3 100644 --- a/libc/calls/getntsyspath.S +++ b/libc/calls/getntsyspath.S @@ -28,8 +28,8 @@ // @return rdi is rdi+edx .text.startup __getntsyspath: - push %rbp - mov %rsp,%rbp + beg + pro push %rdx movpp %rdi,%rcx # call f=%rax(p1=%rcx,p2=%rdx) sub $40,%rsp @@ -55,6 +55,7 @@ __getntsyspath: jne 2f movb $'/',-1(%rdi) 2: .loop 1b - leave + epi ret + end .endfn __getntsyspath,globl,hidden diff --git a/libc/calls/getprogramexecutablename.greg.c b/libc/calls/getprogramexecutablename.greg.c index 8e6e9e1c7..8589bb099 100644 --- a/libc/calls/getprogramexecutablename.greg.c +++ b/libc/calls/getprogramexecutablename.greg.c @@ -96,9 +96,8 @@ static int OldApeLoader(char *s) { static int CopyWithCwd(const char *q, char *p, char *e) { char c; if (*q != '/') { - if (q[0] == '.' && q[1] == '/') { + if (q[0] == '.' && q[1] == '/') q += 2; - } int got = __getcwd(p, e - p - 1 /* '/' */); if (got != -1) { p += got - 1; @@ -118,9 +117,10 @@ static int CopyWithCwd(const char *q, char *p, char *e) { // if q exists then turn it into an absolute path. static int TryPath(const char *q) { - if (!CopyWithCwd(q, g_prog.u.buf, g_prog.u.buf + sizeof(g_prog.u.buf))) { + if (!q) + return 0; + if (!CopyWithCwd(q, g_prog.u.buf, g_prog.u.buf + sizeof(g_prog.u.buf))) return 0; - } return !sys_faccessat(AT_FDCWD, g_prog.u.buf, F_OK, 0); } @@ -129,9 +129,8 @@ static int TryPath(const char *q) { void __init_program_executable_name(void) { if (__program_executable_name && *__program_executable_name != '/' && CopyWithCwd(__program_executable_name, g_prog.u.buf, - g_prog.u.buf + sizeof(g_prog.u.buf))) { + g_prog.u.buf + sizeof(g_prog.u.buf))) __program_executable_name = g_prog.u.buf; - } } static inline void InitProgramExecutableNameImpl(void) { @@ -212,14 +211,12 @@ static inline void InitProgramExecutableNameImpl(void) { } // don't trust argv or envp if set-id. - if (issetugid()) { + if (issetugid()) goto UseEmpty; - } // try argv[0], then then $_. - if (TryPath(__argv[0]) || TryPath(__getenv(__envp, "_").s)) { + if (TryPath(__argv[0]) || TryPath(__getenv(__envp, "_").s)) goto UseBuf; - } // give up and just copy argv[0] into it if ((q = __argv[0])) { diff --git a/libc/calls/getrandom.c b/libc/calls/getrandom.c index 9bb079c77..a0c53383b 100644 --- a/libc/calls/getrandom.c +++ b/libc/calls/getrandom.c @@ -114,7 +114,8 @@ static ssize_t GetDevUrandom(char *p, size_t n, unsigned f) { ssize_t __getrandom(void *p, size_t n, unsigned f) { ssize_t rc; if (IsWindows()) { - rc = ProcessPrng(p, n) ? n : __winerr(); + ProcessPrng(p, n); // never fails + rc = n; } else if (have_getrandom) { if (IsXnu() || IsOpenbsd()) { rc = GetRandomBsd(p, n, GetRandomEntropy); @@ -184,9 +185,7 @@ ssize_t __getrandom(void *p, size_t n, unsigned f) { * @raise EFAULT if the `n` bytes at `p` aren't valid memory * @raise EINTR if we needed to block and a signal was delivered instead * @cancelationpoint - * @asyncsignalsafe * @restartable - * @vforksafe */ ssize_t getrandom(void *p, size_t n, unsigned f) { ssize_t rc; diff --git a/libc/calls/getrlimit.c b/libc/calls/getrlimit.c index de7df079e..d2a826eda 100644 --- a/libc/calls/getrlimit.c +++ b/libc/calls/getrlimit.c @@ -21,6 +21,7 @@ #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" #include "libc/intrin/describeflags.h" +#include "libc/intrin/rlimit.h" #include "libc/intrin/strace.h" #include "libc/runtime/runtime.h" #include "libc/runtime/stack.h" @@ -47,8 +48,7 @@ int getrlimit(int resource, struct rlimit *rlim) { } else if (!IsWindows()) { rc = sys_getrlimit(resource, rlim); } else if (resource == RLIMIT_STACK) { - rlim->rlim_cur = GetStaticStackSize(); - rlim->rlim_max = GetStaticStackSize(); + *rlim = __rlimit_stack_get(); rc = 0; } else if (resource == RLIMIT_AS) { rlim->rlim_cur = __virtualmax; diff --git a/libc/calls/internal.h b/libc/calls/internal.h index 010215788..80ffd0c58 100644 --- a/libc/calls/internal.h +++ b/libc/calls/internal.h @@ -3,6 +3,7 @@ #include "libc/atomic.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/struct/sigval.h" +#include "libc/calls/struct/timespec.h" #include "libc/dce.h" #include "libc/intrin/fds.h" #include "libc/macros.h" @@ -30,8 +31,10 @@ int CountConsoleInputBytes(void); int FlushConsoleInputBytes(void); int64_t GetConsoleInputHandle(void); int64_t GetConsoleOutputHandle(void); +void EchoConsoleNt(const char *, size_t, bool); int IsWindowsExecutable(int64_t, const char16_t *); void InterceptTerminalCommands(const char *, size_t); +void sys_read_nt_wipe_keystrokes(void); forceinline bool __isfdopen(int fd) { return 0 <= fd && fd < g_fds.n && g_fds.p[fd].kind != kFdEmpty; @@ -45,8 +48,8 @@ int _check_signal(bool); int _check_cancel(void); bool _is_canceled(void); int sys_close_nt(int, int); -int _park_norestart(uint32_t, uint64_t); -int _park_restartable(uint32_t, uint64_t); +int _park_norestart(struct timespec, uint64_t); +int _park_restartable(struct timespec, uint64_t); int sys_openat_metal(int, const char *, int, unsigned); #ifdef __x86_64__ diff --git a/libc/calls/makedev.h b/libc/calls/makedev.h index dcd221026..21479c795 100644 --- a/libc/calls/makedev.h +++ b/libc/calls/makedev.h @@ -1,5 +1,6 @@ #ifndef COSMOPOLITAN_LIBC_CALLS_MAKEDEV_H_ #define COSMOPOLITAN_LIBC_CALLS_MAKEDEV_H_ +COSMOPOLITAN_C_START_ uint64_t makedev(uint32_t, uint32_t) libcesque; uint32_t major(uint64_t) libcesque; @@ -9,4 +10,5 @@ uint32_t minor(uint64_t) libcesque; #define minor(x) minor(x) #define makedev(x, y) makedev(x, y) +COSMOPOLITAN_C_END_ #endif /* COSMOPOLITAN_LIBC_CALLS_MAKEDEV_H_ */ diff --git a/libc/calls/metalfile.c b/libc/calls/metalfile.c index d20736e35..5d2c57540 100644 --- a/libc/calls/metalfile.c +++ b/libc/calls/metalfile.c @@ -67,10 +67,9 @@ textstartup void InitializeMetalFile(void) { size_t size = ROUNDUP(_ezip - __executable_start, 4096); // TODO(jart): Restore support for ZIPOS on metal. void *copied_base; - struct DirectMap dm; - dm = sys_mmap_metal(NULL, size, PROT_READ | PROT_WRITE, - MAP_SHARED_linux | MAP_ANONYMOUS_linux, -1, 0); - copied_base = dm.addr; + void *addr = sys_mmap_metal(NULL, size, PROT_READ | PROT_WRITE, + MAP_SHARED_linux | MAP_ANONYMOUS_linux, -1, 0); + copied_base = addr; npassert(copied_base != (void *)-1); memcpy(copied_base, (void *)(BANE + IMAGE_BASE_PHYSICAL), size); __ape_com_base = copied_base; diff --git a/libc/calls/ntspawn.c b/libc/calls/ntspawn.c index b0887c2b6..cd7531148 100644 --- a/libc/calls/ntspawn.c +++ b/libc/calls/ntspawn.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/proc/ntspawn.h" +#include "libc/calls/state.internal.h" #include "libc/calls/struct/sigset.internal.h" #include "libc/calls/syscall_support-nt.internal.h" #include "libc/intrin/strace.h" @@ -38,12 +39,15 @@ #include "libc/nt/struct/procthreadattributelist.h" #include "libc/nt/struct/startupinfo.h" #include "libc/nt/struct/startupinfoex.h" +#include "libc/nt/thunk/msabi.h" #include "libc/proc/ntspawn.h" #include "libc/stdalign.h" #include "libc/str/str.h" #include "libc/sysv/errfuns.h" #ifdef __x86_64__ +__msabi extern typeof(CloseHandle) *const __imp_CloseHandle; + struct SpawnBlock { char16_t path[PATH_MAX]; char16_t cmdline[32767]; @@ -63,10 +67,12 @@ static textwindows ssize_t ntspawn_read(intptr_t fh, char *buf, size_t len) { bool ok; uint32_t got; struct NtOverlapped overlap = {.hEvent = CreateEvent(0, 0, 0, 0)}; - ok = (ReadFile(fh, buf, len, 0, &overlap) || + ok = overlap.hEvent && + (ReadFile(fh, buf, len, 0, &overlap) || GetLastError() == kNtErrorIoPending) && GetOverlappedResult(fh, &overlap, &got, true); - CloseHandle(overlap.hEvent); + if (overlap.hEvent) + __imp_CloseHandle(overlap.hEvent); return ok ? got : -1; } @@ -86,7 +92,7 @@ static textwindows int ntspawn2(struct NtSpawnArgs *a, struct SpawnBlock *sb) { if (fh == -1) return -1; ssize_t got = ntspawn_read(fh, p, pe - p); - CloseHandle(fh); + __imp_CloseHandle(fh); if (got < 3) return enoexec(); pe = p + got; diff --git a/libc/calls/openat-metal.c b/libc/calls/openat-metal.c index 16650f4b3..647af1360 100644 --- a/libc/calls/openat-metal.c +++ b/libc/calls/openat-metal.c @@ -49,11 +49,9 @@ int sys_openat_metal(int dirfd, const char *file, int flags, unsigned mode) { if ((fd = __reservefd(-1)) == -1) return -1; if (!_weaken(calloc) || !_weaken(free)) { - struct DirectMap dm; - dm = sys_mmap_metal(NULL, ROUNDUP(sizeof(struct MetalFile), 4096), - PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, - 0); - state = dm.addr; + state = sys_mmap_metal(NULL, ROUNDUP(sizeof(struct MetalFile), 4096), + PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, + -1, 0); if (state == (void *)-1) return -1; } else { diff --git a/libc/calls/park.c b/libc/calls/park.c index 71f203128..103a6cbdf 100644 --- a/libc/calls/park.c +++ b/libc/calls/park.c @@ -17,37 +17,98 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/internal.h" +#include "libc/calls/sig.internal.h" #include "libc/calls/struct/sigset.h" +#include "libc/calls/struct/timespec.h" +#include "libc/calls/syscall_support-nt.internal.h" +#include "libc/fmt/wintime.internal.h" #include "libc/intrin/atomic.h" +#include "libc/intrin/weaken.h" +#include "libc/nt/events.h" +#include "libc/nt/runtime.h" #include "libc/nt/synchronization.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/sicode.h" +#include "libc/sysv/errfuns.h" #include "libc/thread/posixthread.internal.h" + #ifdef __x86_64__ -// returns 0 on timeout or spurious wakeup +// returns 0 if deadline is reached // raises EINTR if a signal delivery interrupted wait operation // raises ECANCELED if this POSIX thread was canceled in masked mode -textwindows static int _park_thread(uint32_t msdelay, sigset_t waitmask, +textwindows static int _park_thread(struct timespec deadline, sigset_t waitmask, bool restartable) { - if (__sigcheck(waitmask, restartable) == -1) - return -1; - int expect = 0; - atomic_int futex = 0; - struct PosixThread *pt = _pthread_self(); - pt->pt_blkmask = waitmask; - atomic_store_explicit(&pt->pt_blocker, &futex, memory_order_release); - bool32 ok = WaitOnAddress(&futex, &expect, sizeof(int), msdelay); - atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release); - if (ok && __sigcheck(waitmask, restartable) == -1) - return -1; - return 0; + for (;;) { + uint32_t handl = 0; + intptr_t hands[2]; + + // create event object + intptr_t sigev; + if (!(sigev = CreateEvent(0, 0, 0, 0))) + return __winerr(); + hands[handl++] = sigev; + + // create high precision timer if needed + if (memcmp(&deadline, ×pec_max, sizeof(struct timespec))) { + intptr_t hTimer; + if ((hTimer = CreateWaitableTimer(NULL, true, NULL))) { + int64_t due = TimeSpecToWindowsTime(deadline); + if (SetWaitableTimer(hTimer, &due, 0, NULL, NULL, false)) { + hands[handl++] = hTimer; + } else { + CloseHandle(hTimer); + } + } + } + + // perform wait operation + struct PosixThread *pt = _pthread_self(); + pt->pt_event = sigev; + pt->pt_blkmask = waitmask; + atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_EVENT, + memory_order_release); + //!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!// + int sig = 0; + uint32_t wi = 0; + if (!_is_canceled() && + !(_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask)))) + wi = WaitForMultipleObjects(handl, hands, false, -1u); + //!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!// + atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release); + for (int i = 0; i < handl; ++i) + CloseHandle(hands[i]); + + // recursion is now safe + if (wi == 1) + return 0; + if (wi == -1u) + return __winerr(); + int handler_was_called = 0; + if (!sig) { + if (_check_cancel()) + return -1; + if (_weaken(__sig_get)) + sig = _weaken(__sig_get)(waitmask); + } + if (sig) + handler_was_called = _weaken(__sig_relay)(sig, SI_KERNEL, waitmask); + if (_check_cancel()) + return -1; + if (handler_was_called & SIG_HANDLED_NO_RESTART) + return eintr(); + if (handler_was_called & SIG_HANDLED_SA_RESTART) + if (!restartable) + return eintr(); + } } -textwindows int _park_norestart(uint32_t msdelay, sigset_t waitmask) { - return _park_thread(msdelay, waitmask, false); +textwindows int _park_norestart(struct timespec deadline, sigset_t waitmask) { + return _park_thread(deadline, waitmask, false); } -textwindows int _park_restartable(uint32_t msdelay, sigset_t waitmask) { - return _park_thread(msdelay, waitmask, true); +textwindows int _park_restartable(struct timespec deadline, sigset_t waitmask) { + return _park_thread(deadline, waitmask, true); } #endif /* __x86_64__ */ diff --git a/libc/calls/pause-nt.c b/libc/calls/pause-nt.c index 0a43e5089..28e5e4184 100644 --- a/libc/calls/pause-nt.c +++ b/libc/calls/pause-nt.c @@ -17,14 +17,21 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/internal.h" +#include "libc/calls/struct/sigset.internal.h" +#include "libc/calls/struct/timespec.h" #include "libc/calls/syscall_support-nt.internal.h" #ifdef __x86_64__ textwindows int sys_pause_nt(void) { - int rc; - while (!(rc = _park_norestart(-1u, 0))) - donothing; - return rc; + // we don't strictly need to block signals, but it reduces signal + // delivery latency, by preventing other threads from delivering a + // signal asynchronously. it takes about ~5us to deliver a signal + // using SetEvent() whereas it takes ~30us to use SuspendThread(), + // GetThreadContext(), SetThreadContext(), and ResumeThread(). + BLOCK_SIGNALS; + _park_norestart(timespec_max, 0); + ALLOW_SIGNALS; + return -1; } #endif /* __x86_64__ */ diff --git a/libc/calls/pipe-nt.c b/libc/calls/pipe-nt.c index d2ed971e0..40a16c375 100644 --- a/libc/calls/pipe-nt.c +++ b/libc/calls/pipe-nt.c @@ -56,7 +56,6 @@ static textwindows int sys_pipe_nt_impl(int pipefd[2], unsigned flags) { __fds_unlock(); hin = CreateNamedPipe(pipename, kNtPipeAccessInbound | kNtFileFlagOverlapped, mode, 1, PIPE_BUF, PIPE_BUF, 0, &kNtIsInheritable); - __fds_lock(); if (hin != -1) { if ((hout = CreateFile( pipename, kNtGenericWrite, @@ -73,7 +72,6 @@ static textwindows int sys_pipe_nt_impl(int pipefd[2], unsigned flags) { g_fds.p[writer].handle = hout; pipefd[0] = reader; pipefd[1] = writer; - __fds_unlock(); return 0; } else { CloseHandle(hin); @@ -81,7 +79,6 @@ static textwindows int sys_pipe_nt_impl(int pipefd[2], unsigned flags) { } __releasefd(writer); __releasefd(reader); - __fds_unlock(); return -1; } diff --git a/libc/calls/pledge-linux.c b/libc/calls/pledge-linux.c index 07df6bca7..3ea63946c 100644 --- a/libc/calls/pledge-linux.c +++ b/libc/calls/pledge-linux.c @@ -694,6 +694,7 @@ static const uint16_t kPledgeStdio[] = { __NR_linux_sched_getaffinity, // __NR_linux_sched_setaffinity, // __NR_linux_sigtimedwait, // + __NR_linux_getcpu, // }; static const uint16_t kPledgeFlock[] = { diff --git a/libc/calls/poll-nt.c b/libc/calls/poll-nt.c index 339bea6ff..cc015f045 100644 --- a/libc/calls/poll-nt.c +++ b/libc/calls/poll-nt.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/internal.h" +#include "libc/calls/sig.internal.h" #include "libc/calls/state.internal.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/struct/sigset.internal.h" @@ -25,6 +26,7 @@ #include "libc/calls/syscall_support-nt.internal.h" #include "libc/intrin/atomic.h" #include "libc/intrin/fds.h" +#include "libc/intrin/weaken.h" #include "libc/macros.h" #include "libc/nt/console.h" #include "libc/nt/enum/filetype.h" @@ -41,6 +43,7 @@ #include "libc/sock/internal.h" #include "libc/sock/struct/pollfd.h" #include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/sicode.h" #include "libc/sysv/errfuns.h" #include "libc/thread/posixthread.internal.h" #ifdef __x86_64__ @@ -84,7 +87,7 @@ textwindows static int sys_poll_nt_actual(struct pollfd *fds, uint64_t nfds, struct PosixThread *pt; int i, rc, ev, kind, gotsocks; struct sys_pollfd_nt sockfds[64]; - uint32_t cm, fi, wi, sn, pn, avail, waitfor, already_slept; + uint32_t cm, fi, sn, pn, avail, waitfor, already_slept; // ensure revents is cleared for (i = 0; i < nfds; ++i) @@ -228,22 +231,36 @@ textwindows static int sys_poll_nt_actual(struct pollfd *fds, uint64_t nfds, // this ensures low latency for apps like emacs which with no sock // here we shall actually report that something can be written too if (!already_slept) { - if (__sigcheck(waitmask, false)) - return -1; + intptr_t sigev; + if (!(sigev = CreateEvent(0, 0, 0, 0))) + return __winerr(); + filehands[pn] = sigev; pt = _pthread_self(); - filehands[pn] = pt->pt_event = CreateEvent(0, 0, 0, 0); + pt->pt_event = sigev; + pt->pt_blkmask = waitmask; atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_EVENT, memory_order_release); - wi = WaitForMultipleObjects(pn + 1, filehands, 0, waitfor); + //!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!// + int sig = 0; + uint32_t wi = pn; + if (!_is_canceled() && + !(_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask)))) + wi = WaitForMultipleObjects(pn + 1, filehands, 0, waitfor); + //!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!// atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release); - CloseHandle(filehands[pn]); - if (wi == -1u) { + CloseHandle(sigev); + if (wi == -1u) // win32 wait failure return __winerr(); - } else if (wi == pn) { + if (wi == pn) { // our signal event was signalled - if (__sigcheck(waitmask, false)) + int handler_was_called = 0; + if (sig) + handler_was_called = _weaken(__sig_relay)(sig, SI_KERNEL, waitmask); + if (_check_cancel() == -1) return -1; + if (handler_was_called) + return eintr(); } else if ((wi ^ kNtWaitAbandoned) < pn) { // this is possibly because a process or thread was killed fds[fileindices[wi ^ kNtWaitAbandoned]].revents = POLLERR_; @@ -287,8 +304,6 @@ textwindows static int sys_poll_nt_actual(struct pollfd *fds, uint64_t nfds, } else { // should only be possible on kNtWaitTimeout or semaphore abandoned // keep looping for events and we'll catch timeout when appropriate - if (__sigcheck(waitmask, false)) - return -1; } } @@ -303,8 +318,15 @@ textwindows static int sys_poll_nt_actual(struct pollfd *fds, uint64_t nfds, textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds, struct timespec deadline, const sigset_t waitmask) { - uint32_t waitms; int i, n, rc, got = 0; + struct timespec now, next, target; + + // we normally don't check for signals until we decide to wait, since + // it's nice to have functions like write() be unlikely to EINTR, but + // ppoll is a function where users are surely thinking about signals, + // since ppoll actually allows them to block signals everywhere else. + if (__sigcheck(waitmask, false)) + return -1; // fast path if (nfds <= 63) @@ -322,20 +344,28 @@ textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds, } if (got) return got; - if (!(waitms = sys_poll_nt_waitms(deadline))) + now = sys_clock_gettime_monotonic_nt(); + if (timespec_cmp(now, deadline) >= 0) return 0; - if (_park_norestart(waitms, waitmask) == -1) + next = timespec_add(now, timespec_frommillis(POLL_INTERVAL_MS)); + if (timespec_cmp(next, deadline) >= 0) { + target = deadline; + } else { + target = next; + } + if (_park_norestart(target, waitmask) == -1) return -1; } } -textwindows int sys_poll_nt(struct pollfd *fds, uint64_t nfds, uint32_t *ms, +textwindows int sys_poll_nt(struct pollfd *fds, uint64_t nfds, + const struct timespec *relative, const sigset_t *sigmask) { int rc; struct timespec now, timeout, deadline; BLOCK_SIGNALS; - now = ms ? sys_clock_gettime_monotonic_nt() : timespec_zero; - timeout = ms ? timespec_frommillis(*ms) : timespec_max; + now = relative ? sys_clock_gettime_monotonic_nt() : timespec_zero; + timeout = relative ? *relative : timespec_max; deadline = timespec_add(now, timeout); rc = sys_poll_nt_impl(fds, nfds, deadline, sigmask ? *sigmask : _SigMask); ALLOW_SIGNALS; diff --git a/libc/calls/ppoll.c b/libc/calls/ppoll.c index 322937e31..d961a27f4 100644 --- a/libc/calls/ppoll.c +++ b/libc/calls/ppoll.c @@ -25,6 +25,7 @@ #include "libc/dce.h" #include "libc/errno.h" #include "libc/intrin/strace.h" +#include "libc/limits.h" #include "libc/runtime/stack.h" #include "libc/sock/struct/pollfd.h" #include "libc/sock/struct/pollfd.internal.h" @@ -76,10 +77,13 @@ static int ppoll_impl(struct pollfd *fds, size_t nfds, } fdcount = sys_ppoll(fds, nfds, tsp, sigmask, 8); if (fdcount == -1 && errno == ENOSYS) { - int ms; + int64_t ms; errno = e; - if (!timeout || ckd_add(&ms, timeout->tv_sec, - (timeout->tv_nsec + 999999) / 1000000)) { + if (timeout) { + ms = timespec_tomillis(*timeout); + if (ms > INT_MAX) + ms = -1; + } else { ms = -1; } if (sigmask) @@ -89,15 +93,7 @@ static int ppoll_impl(struct pollfd *fds, size_t nfds, sys_sigprocmask(SIG_SETMASK, &oldmask, 0); } } else { - uint32_t ms; - uint32_t *msp; - if (timeout && - !ckd_add(&ms, timeout->tv_sec, (timeout->tv_nsec + 999999) / 1000000)) { - msp = &ms; - } else { - msp = 0; - } - fdcount = sys_poll_nt(fds, nfds, msp, sigmask); + fdcount = sys_poll_nt(fds, nfds, timeout, sigmask); } if (IsOpenbsd() && fdcount != -1) { diff --git a/libc/calls/pselect.c b/libc/calls/pselect.c index 9ecf8490f..93c7f495f 100644 --- a/libc/calls/pselect.c +++ b/libc/calls/pselect.c @@ -67,7 +67,6 @@ int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask) { int rc; - struct timeval tv, *tvp; struct timespec ts, *tsp; struct { const sigset_t *s; @@ -111,24 +110,17 @@ int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, rc = sys_pselect(nfds, readfds, writefds, exceptfds, (struct timespec *)timeout, sigmask); } else { - if (timeout) { - tv.tv_sec = timeout->tv_sec; - tv.tv_usec = timeout->tv_nsec / 1000; - tvp = &tv; - } else { - tvp = 0; - } - rc = sys_select_nt(nfds, readfds, writefds, exceptfds, tvp, sigmask); + rc = sys_select_nt(nfds, readfds, writefds, exceptfds, timeout, sigmask); } } END_CANCELATION_POINT; STRACE("pselect(%d, %s → [%s], %s → [%s], %s → [%s], %s, %s) → %d% m", nfds, - DescribeFdSet(rc, nfds, old_readfds_ptr), + DescribeFdSet(0, nfds, old_readfds_ptr), DescribeFdSet(rc, nfds, readfds), - DescribeFdSet(rc, nfds, old_writefds_ptr), + DescribeFdSet(0, nfds, old_writefds_ptr), DescribeFdSet(rc, nfds, writefds), - DescribeFdSet(rc, nfds, old_exceptfds_ptr), + DescribeFdSet(0, nfds, old_exceptfds_ptr), DescribeFdSet(rc, nfds, exceptfds), // DescribeTimespec(0, timeout), // DescribeSigset(0, sigmask), rc); diff --git a/libc/calls/read-nt.c b/libc/calls/read-nt.c index c4da9d610..b50f428e2 100644 --- a/libc/calls/read-nt.c +++ b/libc/calls/read-nt.c @@ -48,6 +48,7 @@ #include "libc/nt/enum/wait.h" #include "libc/nt/errors.h" #include "libc/nt/events.h" +#include "libc/nt/memory.h" #include "libc/nt/runtime.h" #include "libc/nt/struct/inputrecord.h" #include "libc/nt/synchronization.h" @@ -127,33 +128,46 @@ struct Keystrokes { bool ohno_decckm; bool bypass_mode; uint16_t utf16hs; - int16_t freekeys; + size_t free_keys; int64_t cin, cot; struct Dll *list; struct Dll *line; struct Dll *free; - pthread_mutex_t lock; - struct Keystroke pool[512]; }; static struct Keystrokes __keystroke; +static pthread_mutex_t __keystroke_lock = PTHREAD_MUTEX_INITIALIZER; -textwindows void WipeKeystrokes(void) { +textwindows void sys_read_nt_wipe_keystrokes(void) { bzero(&__keystroke, sizeof(__keystroke)); + _pthread_mutex_wipe_np(&__keystroke_lock); } textwindows static void FreeKeystrokeImpl(struct Dll *key) { dll_make_first(&__keystroke.free, key); - ++__keystroke.freekeys; + ++__keystroke.free_keys; +} + +textwindows static struct Keystroke *AllocKeystroke(void) { + struct Keystroke *k; + if (!(k = HeapAlloc(GetProcessHeap(), 0, sizeof(struct Keystroke)))) + return 0; + dll_init(&k->elem); + return k; } textwindows static struct Keystroke *NewKeystroke(void) { - struct Dll *e = dll_first(__keystroke.free); - if (!e) // See MIN(freekeys) before ReadConsoleInput() - __builtin_trap(); - struct Keystroke *k = KEYSTROKE_CONTAINER(e); - dll_remove(&__keystroke.free, &k->elem); - --__keystroke.freekeys; + struct Dll *e; + struct Keystroke *k; + if ((e = dll_first(__keystroke.free))) { + dll_remove(&__keystroke.free, e); + k = KEYSTROKE_CONTAINER(e); + --__keystroke.free_keys; + } else { + // PopulateKeystrokes() should make this branch impossible + if (!(k = AllocKeystroke())) + return 0; + } k->buflen = 0; return k; } @@ -169,15 +183,22 @@ textwindows static void FreeKeystrokes(struct Dll **list) { FreeKeystroke(list, key); } +textwindows static void PopulateKeystrokes(size_t want) { + struct Keystroke *k; + while (__keystroke.free_keys < want) { + if ((k = AllocKeystroke())) { + FreeKeystrokeImpl(&k->elem); + } else { + break; + } + } +} + textwindows static void OpenConsole(void) { __keystroke.cin = CreateFile(u"CONIN$", kNtGenericRead | kNtGenericWrite, kNtFileShareRead, 0, kNtOpenExisting, 0, 0); __keystroke.cot = CreateFile(u"CONOUT$", kNtGenericRead | kNtGenericWrite, kNtFileShareWrite, 0, kNtOpenExisting, 0, 0); - for (int i = 0; i < ARRAYLEN(__keystroke.pool); ++i) { - dll_init(&__keystroke.pool[i].elem); - FreeKeystrokeImpl(&__keystroke.pool[i].elem); - } } textwindows static int AddSignal(int sig) { @@ -191,11 +212,11 @@ textwindows static void InitConsole(void) { } textwindows static void LockKeystrokes(void) { - pthread_mutex_lock(&__keystroke.lock); + _pthread_mutex_lock(&__keystroke_lock); } textwindows static void UnlockKeystrokes(void) { - pthread_mutex_unlock(&__keystroke.lock); + _pthread_mutex_unlock(&__keystroke_lock); } textwindows int64_t GetConsoleInputHandle(void) { @@ -320,9 +341,12 @@ textwindows static int ProcessKeyEvent(const struct NtInputRecord *r, char *p) { // note we define _POSIX_VDISABLE as zero // tcsetattr() lets anyone reconfigure these keybindings if (c && !(__ttyconf.magic & kTtyNoIsigs) && !__keystroke.bypass_mode) { + char b[] = {c}; if (c == __ttyconf.vintr) { + EchoConsoleNt(b, 1, false); return AddSignal(SIGINT); } else if (c == __ttyconf.vquit) { + EchoConsoleNt(b, 1, false); return AddSignal(SIGQUIT); } } @@ -457,7 +481,8 @@ textwindows static void WriteCtl(const char *p, size_t n, bool escape_harder) { } } -textwindows static void EchoTty(const char *p, size_t n, bool escape_harder) { +textwindows void EchoConsoleNt(const char *p, size_t n, bool escape_harder) { + InitConsole(); if (!(__ttyconf.magic & kTtySilence)) { if (__ttyconf.magic & kTtyEchoRaw) { WriteTty(p, n); @@ -514,14 +539,12 @@ textwindows static void IngestConsoleInputRecord(struct NtInputRecord *r) { !(__ttyconf.magic & kTtyNoIexten)) { // IEXTEN if (__keystroke.bypass_mode) { struct Keystroke *k = NewKeystroke(); + if (!k) + return; memcpy(k->buf, buf, sizeof(k->buf)); k->buflen = len; dll_make_last(&__keystroke.line, &k->elem); - EchoTty(buf, len, true); - if (!__keystroke.freekeys) { - dll_make_last(&__keystroke.list, __keystroke.line); - __keystroke.line = 0; - } + EchoConsoleNt(buf, len, true); __keystroke.bypass_mode = false; return; } else if (len == 1 && buf[0] && // @@ -611,12 +634,14 @@ textwindows static void IngestConsoleInputRecord(struct NtInputRecord *r) { // allocate object to hold keystroke struct Keystroke *k = NewKeystroke(); + if (!k) + return; memcpy(k->buf, buf, sizeof(k->buf)); k->buflen = len; // echo input if it was successfully recorded // assuming the win32 console isn't doing it already - EchoTty(buf, len, false); + EchoConsoleNt(buf, len, false); // save keystroke to appropriate list if (__ttyconf.magic & kTtyUncanon) { @@ -624,12 +649,12 @@ textwindows static void IngestConsoleInputRecord(struct NtInputRecord *r) { } else { dll_make_last(&__keystroke.line, &k->elem); - // flush canonical mode line if oom or enter - if (!__keystroke.freekeys || (len == 1 && buf[0] && - ((buf[0] & 255) == '\n' || // - (buf[0] & 255) == __ttyconf.veol || // - ((buf[0] & 255) == __ttyconf.veol2 && - !(__ttyconf.magic & kTtyNoIexten))))) { + // flush canonical mode line on enter + if (len == 1 && buf[0] && + ((buf[0] & 255) == '\n' || // + (buf[0] & 255) == __ttyconf.veol || // + ((buf[0] & 255) == __ttyconf.veol2 && + !(__ttyconf.magic & kTtyNoIexten)))) { dll_make_last(&__keystroke.list, __keystroke.line); __keystroke.line = 0; } @@ -640,15 +665,17 @@ textwindows static void IngestConsoleInput(void) { uint32_t i, n; struct NtInputRecord records[16]; for (;;) { - if (!__keystroke.freekeys) - return; if (__keystroke.end_of_file) return; if (!GetNumberOfConsoleInputEvents(__keystroke.cin, &n)) goto UnexpectedEof; - if (!n || !__keystroke.freekeys) + if (n > ARRAYLEN(records)) + n = ARRAYLEN(records); + PopulateKeystrokes(n + 1); + if (n > __keystroke.free_keys) + n = __keystroke.free_keys; + if (!n) return; - n = MIN(__keystroke.freekeys, MIN(ARRAYLEN(records), n)); if (!ReadConsoleInput(__keystroke.cin, records, n, &n)) goto UnexpectedEof; for (i = 0; i < n && !__keystroke.end_of_file; ++i) @@ -970,8 +997,10 @@ textwindows ssize_t ReadBuffer(int fd, void *data, size_t size, int64_t offset, if (f->kind == kFdDevNull) return 0; - if (f->kind == kFdDevRandom) - return ProcessPrng(data, size) ? size : __winerr(); + if (f->kind == kFdDevRandom) { + ProcessPrng(data, size); + return size; + } if (f->kind == kFdConsole) return ReadFromConsole(f, data, size, waitmask); diff --git a/libc/calls/readwrite-nt.c b/libc/calls/readwrite-nt.c index e1c059223..1c983ca80 100644 --- a/libc/calls/readwrite-nt.c +++ b/libc/calls/readwrite-nt.c @@ -16,7 +16,6 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/assert.h" #include "libc/calls/createfileflags.internal.h" #include "libc/calls/internal.h" #include "libc/calls/sig.internal.h" @@ -170,14 +169,11 @@ sys_readwrite_nt(int fd, void *data, size_t size, ssize_t offset, } // the i/o operation was successfully canceled - if (got_eagain) { - unassert(!got_sig); + if (got_eagain) return eagain(); - } // it's now reasonable to report semaphore creation error if (other_error) { - unassert(!got_sig); errno = __dos2errno(other_error); return -1; } diff --git a/libc/calls/select-nt.c b/libc/calls/select-nt.c index cbb6797bf..0d9e2f2e0 100644 --- a/libc/calls/select-nt.c +++ b/libc/calls/select-nt.c @@ -25,7 +25,6 @@ #include "libc/sock/sock.h" #include "libc/sock/struct/pollfd.h" #include "libc/sock/struct/pollfd.internal.h" -#include "libc/stdckdint.h" #include "libc/sysv/consts/poll.h" #include "libc/sysv/errfuns.h" #ifdef __x86_64__ @@ -44,7 +43,7 @@ // int sys_select_nt(int nfds, fd_set *readfds, fd_set *writefds, - fd_set *exceptfds, struct timeval *timeout, + fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask) { int pfds = 0; @@ -68,21 +67,8 @@ int sys_select_nt(int nfds, fd_set *readfds, fd_set *writefds, } } - // convert the wait time to a word - uint32_t millis; - if (!timeout) { - millis = -1u; - } else { - int64_t ms = timeval_tomillis(*timeout); - if (ms < 0 || ms > UINT32_MAX) { - millis = -1u; - } else { - millis = ms; - } - } - // call our nt poll implementation - int fdcount = sys_poll_nt(fds, pfds, &millis, sigmask); + int fdcount = sys_poll_nt(fds, pfds, timeout, sigmask); if (fdcount == -1) return -1; @@ -115,10 +101,6 @@ int sys_select_nt(int nfds, fd_set *readfds, fd_set *writefds, } } - // store remaining time back in caller's timeval - if (timeout) - *timeout = timeval_frommillis(millis); - return bits; } diff --git a/libc/calls/select.c b/libc/calls/select.c index e90f69bdb..93704b269 100644 --- a/libc/calls/select.c +++ b/libc/calls/select.c @@ -31,10 +31,7 @@ * such as out-of-band data on a socket; it is equivalent to POLLPRI * in the revents of poll() * @param timeout may be null which means to block indefinitely; cosmo's - * implementation of select() never modifies this parameter which is - * how most platforms except Linux work which modifies it to reflect - * elapsed time, noting that POSIX permits either behavior therefore - * portable code should assume that timeout memory becomes undefined + * implementation of select() never modifies this parameter * @raise E2BIG if we exceeded the 64 socket limit on Windows * @raise ECANCELED if thread was cancelled in masked mode * @raise EINTR if signal was delivered diff --git a/libc/calls/setgroups.c b/libc/calls/setgroups.c index d030239bb..534f5962a 100644 --- a/libc/calls/setgroups.c +++ b/libc/calls/setgroups.c @@ -21,7 +21,6 @@ #include "libc/dce.h" #include "libc/intrin/describeflags.h" #include "libc/intrin/strace.h" -#include "libc/stdckdint.h" #include "libc/sysv/errfuns.h" /** diff --git a/libc/calls/setrlimit.c b/libc/calls/setrlimit.c index 6b8328489..0a2b12ffa 100644 --- a/libc/calls/setrlimit.c +++ b/libc/calls/setrlimit.c @@ -23,6 +23,7 @@ #include "libc/dce.h" #include "libc/errno.h" #include "libc/intrin/describeflags.h" +#include "libc/intrin/rlimit.h" #include "libc/intrin/strace.h" #include "libc/macros.h" #include "libc/runtime/runtime.h" @@ -88,10 +89,12 @@ int setrlimit(int resource, const struct rlimit *rlim) { } else if (!IsWindows() && !(IsNetbsd() && resource == RLIMIT_AS)) { rc = sys_setrlimit(resource, rlim); } else if (resource == RLIMIT_STACK) { - rc = enotsup(); + rc = 0; } else { rc = einval(); } + if (!rc && resource == RLIMIT_STACK) + __rlimit_stack_set(*rlim); // so __rlimit_stack_get() works on all OSes if (resource == RLIMIT_AS) { __virtualmax = rlim->rlim_cur; errno = olde; diff --git a/libc/calls/shm_path_np.c b/libc/calls/shm_path_np.c index 42df957c4..dc5813b8a 100644 --- a/libc/calls/shm_path_np.c +++ b/libc/calls/shm_path_np.c @@ -35,9 +35,8 @@ void shm_path_np(const char *name, char buf[hasatleast 78]) { const char *a; uint8_t digest[BLAKE2B256_DIGEST_LENGTH]; a = "/tmp/", n = 5; - if (IsLinux() && isdirectory("/dev/shm")) { + if (IsLinux() && isdirectory("/dev/shm")) a = "/dev/shm/", n = 9; - } BLAKE2B256(name, strlen(name), digest); p = mempcpy(buf, a, n); p = hexpcpy(p, digest, BLAKE2B256_DIGEST_LENGTH); diff --git a/libc/calls/sig.c b/libc/calls/sig.c deleted file mode 100644 index fc2275e8d..000000000 --- a/libc/calls/sig.c +++ /dev/null @@ -1,621 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│ vi: set et 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/sysv/consts/sig.h" -#include "ape/sections.internal.h" -#include "libc/calls/calls.h" -#include "libc/calls/sig.internal.h" -#include "libc/calls/state.internal.h" -#include "libc/calls/struct/sigaction.h" -#include "libc/calls/struct/siginfo.h" -#include "libc/calls/struct/sigset.internal.h" -#include "libc/calls/struct/ucontext.internal.h" -#include "libc/calls/ucontext.h" -#include "libc/dce.h" -#include "libc/errno.h" -#include "libc/intrin/atomic.h" -#include "libc/intrin/bsf.h" -#include "libc/intrin/describebacktrace.h" -#include "libc/intrin/dll.h" -#include "libc/intrin/kprintf.h" -#include "libc/intrin/strace.h" -#include "libc/intrin/weaken.h" -#include "libc/nt/console.h" -#include "libc/nt/enum/context.h" -#include "libc/nt/enum/exceptionhandleractions.h" -#include "libc/nt/enum/signal.h" -#include "libc/nt/enum/status.h" -#include "libc/nt/events.h" -#include "libc/nt/runtime.h" -#include "libc/nt/signals.h" -#include "libc/nt/struct/ntexceptionpointers.h" -#include "libc/nt/synchronization.h" -#include "libc/nt/thread.h" -#include "libc/runtime/symbols.internal.h" -#include "libc/str/str.h" -#include "libc/sysv/consts/sa.h" -#include "libc/sysv/consts/sicode.h" -#include "libc/sysv/consts/ss.h" -#include "libc/thread/posixthread.internal.h" -#ifdef __x86_64__ - -/** - * @fileoverview Cosmopolitan Signals for Windows. - */ - -struct SignalFrame { - unsigned rva; - unsigned flags; - siginfo_t si; - ucontext_t ctx; -}; - -static textwindows bool __sig_ignored_by_default(int sig) { - return sig == SIGURG || // - sig == SIGCONT || // - sig == SIGCHLD || // - sig == SIGWINCH; -} - -textwindows bool __sig_ignored(int sig) { - return __sighandrvas[sig] == (intptr_t)SIG_IGN || - (__sighandrvas[sig] == (intptr_t)SIG_DFL && - __sig_ignored_by_default(sig)); -} - -textwindows void __sig_delete(int sig) { - struct Dll *e; - atomic_fetch_and_explicit(&__sig.pending, ~(1ull << (sig - 1)), - memory_order_relaxed); - _pthread_lock(); - for (e = dll_last(_pthread_list); e; e = dll_prev(_pthread_list, e)) - atomic_fetch_and_explicit(&POSIXTHREAD_CONTAINER(e)->tib->tib_sigpending, - ~(1ull << (sig - 1)), memory_order_relaxed); - _pthread_unlock(); -} - -static textwindows int __sig_getter(atomic_ulong *sigs, sigset_t masked) { - int sig; - sigset_t bit, pending, deliverable; - for (;;) { - pending = atomic_load_explicit(sigs, memory_order_acquire); - if ((deliverable = pending & ~masked)) { - sig = bsfl(deliverable) + 1; - bit = 1ull << (sig - 1); - if (atomic_fetch_and_explicit(sigs, ~bit, memory_order_acq_rel) & bit) - return sig; - } else { - return 0; - } - } -} - -textwindows int __sig_get(sigset_t masked) { - int sig; - if (!(sig = __sig_getter(&__get_tls()->tib_sigpending, masked))) - sig = __sig_getter(&__sig.pending, masked); - return sig; -} - -static textwindows bool __sig_should_use_altstack(unsigned flags, - struct CosmoTib *tib) { - if (!(flags & SA_ONSTACK)) - return false; // signal handler didn't enable it - if (!tib->tib_sigstack_size) - return false; // sigaltstack() wasn't installed on this thread - if (tib->tib_sigstack_flags & SS_DISABLE) - return false; // sigaltstack() on this thread was disabled by user - char *bp = __builtin_frame_address(0); - if (tib->tib_sigstack_addr <= bp && - bp <= tib->tib_sigstack_addr + tib->tib_sigstack_size) - return false; // we're already on the alternate stack - return true; -} - -static textwindows wontreturn void __sig_terminate(int sig) { - TerminateThisProcess(sig); -} - -static textwindows bool __sig_start(struct PosixThread *pt, int sig, - unsigned *rva, unsigned *flags) { - *rva = __sighandrvas[sig]; - *flags = __sighandflags[sig]; - if (*rva == (intptr_t)SIG_IGN || - (*rva == (intptr_t)SIG_DFL && __sig_ignored_by_default(sig))) { - STRACE("ignoring %G", sig); - return false; - } - if (atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) & - (1ull << (sig - 1))) { - STRACE("enqueing %G on %d", sig, _pthread_tid(pt)); - atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1), - memory_order_relaxed); - return false; - } - if (*rva == (intptr_t)SIG_DFL) { - STRACE("terminating on %G due to no handler", sig); - __sig_terminate(sig); - } - return true; -} - -static textwindows sigaction_f __sig_handler(unsigned rva) { - atomic_fetch_add_explicit(&__sig.count, 1, memory_order_relaxed); - return (sigaction_f)(__executable_start + rva); -} - -textwindows int __sig_raise(volatile int sig, int sic) { - - // bitset of kinds of handlers called - volatile int handler_was_called = 0; - - // loop over pending signals - ucontext_t ctx; - getcontext(&ctx); - if (!sig) { - if ((sig = __sig_get(ctx.uc_sigmask))) { - sic = SI_KERNEL; - } else { - return handler_was_called; - } - } - - // process signal(s) - unsigned rva, flags; - struct PosixThread *pt = _pthread_self(); - if (__sig_start(pt, sig, &rva, &flags)) { - if (flags & SA_RESETHAND) { - STRACE("resetting %G handler", sig); - __sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL; - } - - // update the signal mask in preparation for signal handller - sigset_t blocksigs = __sighandmask[sig]; - if (!(flags & SA_NODEFER)) - blocksigs |= 1ull << (sig - 1); - ctx.uc_sigmask = atomic_fetch_or_explicit(&pt->tib->tib_sigmask, blocksigs, - memory_order_acquire); - - // call the user's signal handler - char ssbuf[128]; - siginfo_t si = {.si_signo = sig, .si_code = sic}; - STRACE("__sig_raise(%G, %t) mask %s", sig, __sig_handler(rva), - _DescribeSigset(ssbuf, 0, (sigset_t *)&pt->tib->tib_sigmask)); - __sig_handler(rva)(sig, &si, &ctx); - - // record this handler - if (flags & SA_RESTART) { - handler_was_called |= SIG_HANDLED_SA_RESTART; - } else { - handler_was_called |= SIG_HANDLED_NO_RESTART; - } - } - - // restore sigmask - // loop back to top - // jump where handler says - sig = 0; - return setcontext(&ctx); -} - -textwindows int __sig_relay(int sig, int sic, sigset_t waitmask) { - sigset_t m; - int handler_was_called; - m = atomic_exchange_explicit(&__get_tls()->tib_sigmask, waitmask, - memory_order_acquire); - handler_was_called = __sig_raise(sig, SI_KERNEL); - atomic_store_explicit(&__get_tls()->tib_sigmask, m, memory_order_release); - return handler_was_called; -} - -// cancels blocking operations being performed by signaled thread -textwindows void __sig_cancel(struct PosixThread *pt, int sig, unsigned flags) { - atomic_int *blocker; - blocker = atomic_load_explicit(&pt->pt_blocker, memory_order_acquire); - if (!blocker) { - STRACE("%G sent to %d asynchronously", sig, _pthread_tid(pt)); - return; - } - // threads can create semaphores on an as-needed basis - if (blocker == PT_BLOCKER_EVENT) { - STRACE("%G set %d's event object", sig, _pthread_tid(pt)); - SetEvent(pt->pt_event); - return; - } - // all other blocking ops that aren't overlap should use futexes - // we force restartable futexes to churn by waking w/o releasing - STRACE("%G waking %d's futex", sig, _pthread_tid(pt)); - WakeByAddressSingle(blocker); -} - -// the user's signal handler callback is wrapped with this trampoline -static textwindows wontreturn void __sig_tramp(struct SignalFrame *sf) { - int sig = sf->si.si_signo; - struct CosmoTib *tib = __get_tls(); - struct PosixThread *pt = (struct PosixThread *)tib->tib_pthread; - for (;;) { - - // update the signal mask in preparation for signal handller - sigset_t blocksigs = __sighandmask[sig]; - if (!(sf->flags & SA_NODEFER)) - blocksigs |= 1ull << (sig - 1); - sf->ctx.uc_sigmask = atomic_fetch_or_explicit(&tib->tib_sigmask, blocksigs, - memory_order_acquire); - - // call the user's signal handler - char ssbuf[2][128]; - STRACE("__sig_tramp(%G, %t) mask %s → %s", sig, __sig_handler(sf->rva), - _DescribeSigset(ssbuf[0], 0, &sf->ctx.uc_sigmask), - _DescribeSigset(ssbuf[1], 0, (sigset_t *)&tib->tib_sigmask)); - __sig_handler(sf->rva)(sig, &sf->si, &sf->ctx); - - // restore the signal mask that was used by the interrupted code - // this may have been modified by the signal handler in the callback - atomic_store_explicit(&tib->tib_sigmask, sf->ctx.uc_sigmask, - memory_order_release); - - // jump back into original code if there aren't any pending signals - do { - if (!(sig = __sig_get(sf->ctx.uc_sigmask))) - __sig_restore(&sf->ctx); - } while (!__sig_start(pt, sig, &sf->rva, &sf->flags)); - - // tail recurse into another signal handler - sf->si.si_signo = sig; - sf->si.si_code = SI_KERNEL; - if (sf->flags & SA_RESETHAND) { - STRACE("resetting %G handler", sig); - __sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL; - } - } -} - -// sends signal to another specific thread which is ref'd -static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) { - unsigned rva = __sighandrvas[sig]; - unsigned flags = __sighandflags[sig]; - - // do nothing if signal is ignored - if (rva == (intptr_t)SIG_IGN || - (rva == (intptr_t)SIG_DFL && __sig_ignored_by_default(sig))) { - STRACE("ignoring %G", sig); - return 0; - } - - // we can't preempt threads that masked sig or are blocked - if (atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) & - (1ull << (sig - 1))) { - atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1), - memory_order_relaxed); - __sig_cancel(pt, sig, flags); - return 0; - } - - // if there's no handler then killing a thread kills the process - if (rva == (intptr_t)SIG_DFL) { - STRACE("terminating on %G due to no handler", sig); - __sig_terminate(sig); - } - - // ignore signals already pending - uintptr_t th = _pthread_syshand(pt); - if (atomic_load_explicit(&pt->tib->tib_sigpending, memory_order_acquire) & - (1ull << (sig - 1))) { - return 0; - } - - // take control of thread - // suspending the thread happens asynchronously - // however getting the context blocks until it's frozen - static pthread_spinlock_t killer_lock; - pthread_spin_lock(&killer_lock); - if (SuspendThread(th) == -1u) { - STRACE("SuspendThread failed w/ %d", GetLastError()); - pthread_spin_unlock(&killer_lock); - return ESRCH; - } - struct NtContext nc; - nc.ContextFlags = kNtContextFull; - if (!GetThreadContext(th, &nc)) { - STRACE("GetThreadContext failed w/ %d", GetLastError()); - ResumeThread(th); - pthread_spin_unlock(&killer_lock); - return ESRCH; - } - pthread_spin_unlock(&killer_lock); - - // we can't preempt threads that masked sig or are blocked - // we can't preempt threads that are running in win32 code - // so we shall unblock the thread and let it signal itself - if ((atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) & - (1ull << (sig - 1))) || - !((uintptr_t)__executable_start <= nc.Rip && - nc.Rip < (uintptr_t)__privileged_start)) { - atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1), - memory_order_relaxed); - ResumeThread(th); - __sig_cancel(pt, sig, flags); - return 0; - } - - // preferring to live dangerously - // the thread will be signaled asynchronously - if (flags & SA_RESETHAND) { - STRACE("resetting %G handler", sig); - __sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL; - } - - // inject call to trampoline function into thread - uintptr_t sp; - if (__sig_should_use_altstack(flags, pt->tib)) { - sp = (uintptr_t)pt->tib->tib_sigstack_addr + pt->tib->tib_sigstack_size; - } else { - sp = nc.Rsp; - } - sp -= sizeof(struct SignalFrame); - sp &= -16; - struct SignalFrame *sf = (struct SignalFrame *)sp; - _ntcontext2linux(&sf->ctx, &nc); - bzero(&sf->si, sizeof(sf->si)); - sf->rva = rva; - sf->flags = flags; - sf->si.si_code = sic; - sf->si.si_signo = sig; - *(uintptr_t *)(sp -= sizeof(uintptr_t)) = nc.Rip; - nc.Rip = (intptr_t)__sig_tramp; - nc.Rdi = (intptr_t)sf; - nc.Rsp = sp; - if (!SetThreadContext(th, &nc)) { - STRACE("SetThreadContext failed w/ %d", GetLastError()); - return ESRCH; - } - ResumeThread(th); - __sig_cancel(pt, sig, flags); - return 0; -} - -// sends signal to another specific thread -textwindows int __sig_kill(struct PosixThread *pt, int sig, int sic) { - int rc; - BLOCK_SIGNALS; - rc = __sig_killer(pt, sig, sic); - ALLOW_SIGNALS; - return rc; -} - -// sends signal to any other thread -textwindows void __sig_generate(int sig, int sic) { - struct Dll *e; - struct PosixThread *pt, *mark = 0; - if (__sig_ignored(sig)) { - STRACE("ignoring %G", sig); - return; - } - if (__sighandrvas[sig] == (intptr_t)SIG_DFL) { - STRACE("terminating on %G due to no handler", sig); - __sig_terminate(sig); - } - if (atomic_load_explicit(&__sig.pending, memory_order_acquire) & - (1ull << (sig - 1))) { - return; - } - BLOCK_SIGNALS; - _pthread_lock(); - for (e = dll_first(_pthread_list); e; e = dll_next(_pthread_list, e)) { - pt = POSIXTHREAD_CONTAINER(e); - // we don't want to signal ourself - if (pt == _pthread_self()) - continue; - // we don't want to signal a thread that isn't running - if (atomic_load_explicit(&pt->pt_status, memory_order_acquire) >= - kPosixThreadTerminated) { - continue; - } - // choose this thread if it isn't masking sig - if (!(atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) & - (1ull << (sig - 1)))) { - _pthread_ref(pt); - mark = pt; - break; - } - // if a thread is blocking then we check to see if it's planning - // to unblock our sig once the wait operation is completed; when - // that's the case we can cancel the thread's i/o to deliver sig - if (atomic_load_explicit(&pt->pt_blocker, memory_order_acquire) && - !(pt->pt_blkmask & (1ull << (sig - 1)))) { - _pthread_ref(pt); - mark = pt; - break; - } - } - _pthread_unlock(); - if (mark) { - __sig_killer(mark, sig, sic); - _pthread_unref(mark); - } else { - atomic_fetch_or_explicit(&__sig.pending, 1ull << (sig - 1), - memory_order_relaxed); - } - ALLOW_SIGNALS; -} - -static textwindows char *__sig_stpcpy(char *d, const char *s) { - size_t i; - for (i = 0;; ++i) - if (!(d[i] = s[i])) - return d + i; -} - -static textwindows wontreturn void __sig_death(int sig, const char *thing) { -#ifndef TINY - intptr_t hStderr; - char sigbuf[21], s[128], *p; - hStderr = GetStdHandle(kNtStdErrorHandle); - p = __sig_stpcpy(s, "Terminating on "); - p = __sig_stpcpy(p, thing); - p = __sig_stpcpy(p, strsignal_r(sig, sigbuf)); - p = __sig_stpcpy(p, - ". Pass --strace and/or ShowCrashReports() for details.\n"); - WriteFile(hStderr, s, p - s, 0, 0); -#endif - __sig_terminate(sig); -} - -static textwindows void __sig_unmaskable(struct NtExceptionPointers *ep, - int code, int sig, - struct CosmoTib *tib) { - - // log vital crash information reliably for --strace before doing much - // we don't print this without the flag since raw numbers scare people - // this needs at least one page of stack memory in order to get logged - // otherwise it'll print a warning message about the lack of stack mem - STRACE("win32 vectored exception 0x%08Xu raising %G " - "cosmoaddr2line %s %lx %s", - ep->ExceptionRecord->ExceptionCode, sig, - _weaken(FindDebugBinary) ? _weaken(FindDebugBinary)() - : program_invocation_name, - ep->ContextRecord->Rip, - DescribeBacktrace((struct StackFrame *)ep->ContextRecord->Rbp)); - - // if the user didn't install a signal handler for this unmaskable - // exception, then print a friendly helpful hint message to stderr - unsigned rva = __sighandrvas[sig]; - if (rva == (intptr_t)SIG_DFL || rva == (intptr_t)SIG_IGN) - __sig_death(sig, "uncaught "); - - // if this signal handler is configured to auto-reset to the default - // then that reset needs to happen before the user handler is called - unsigned flags = __sighandflags[sig]; - if (flags & SA_RESETHAND) { - STRACE("resetting %G handler", sig); - __sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL; - } - - // determine the true memory address at which fault occurred - // if this is a stack overflow then reapply guard protection - void *si_addr; - if (ep->ExceptionRecord->ExceptionCode == kNtSignalGuardPage) { - si_addr = (void *)ep->ExceptionRecord->ExceptionInformation[1]; - } else { - si_addr = ep->ExceptionRecord->ExceptionAddress; - } - - // call the user signal handler - // and a modifiable view of the faulting code's cpu state - // temporarily replace signal mask while calling crash handler - // abort process if sig is already blocked to avoid crash loop - // note ucontext_t is a hefty data structures on top of NtContext - ucontext_t ctx = {0}; - siginfo_t si = {.si_signo = sig, .si_code = code, .si_addr = si_addr}; - _ntcontext2linux(&ctx, ep->ContextRecord); - sigset_t blocksigs = __sighandmask[sig]; - if (!(flags & SA_NODEFER)) - blocksigs |= 1ull << (sig - 1); - ctx.uc_sigmask = atomic_fetch_or_explicit(&tib->tib_sigmask, blocksigs, - memory_order_acquire); - if (ctx.uc_sigmask & (1ull << (sig - 1))) { - __sig_death(sig, "masked "); - __sig_terminate(sig); - } - __sig_handler(rva)(sig, &si, &ctx); - atomic_store_explicit(&tib->tib_sigmask, ctx.uc_sigmask, - memory_order_release); - _ntlinux2context(ep->ContextRecord, &ctx); -} - -void __stack_call(struct NtExceptionPointers *, int, int, struct CosmoTib *, - void (*)(struct NtExceptionPointers *, int, int, - struct CosmoTib *), - void *); - -// abashed the devil stood -// and felt how awful goodness is -__msabi dontinstrument unsigned __sig_crash(struct NtExceptionPointers *ep) { - - // translate win32 to unix si_signo and si_code - int code, sig = __sig_crash_sig(ep->ExceptionRecord->ExceptionCode, &code); - - // advance the instruction pointer to skip over debugger breakpoints - // this behavior is consistent with how unix kernels are implemented - if (sig == SIGTRAP) { - ep->ContextRecord->Rip++; - if (__sig_ignored(sig)) - return kNtExceptionContinueExecution; - } - - // win32 stack overflow detection executes INSIDE the guard page - // thus switch to the alternate signal stack as soon as possible - struct CosmoTib *tib = __get_tls(); - unsigned flags = __sighandflags[sig]; - if (__sig_should_use_altstack(flags, tib)) { - __stack_call(ep, code, sig, tib, __sig_unmaskable, - tib->tib_sigstack_addr + tib->tib_sigstack_size); - } else { - __sig_unmaskable(ep, code, sig, tib); - } - - // resume running user program - // hopefully the user fixed the cpu state - // otherwise the crash will keep happening - return kNtExceptionContinueExecution; -} - -static textwindows int __sig_console_sig(uint32_t dwCtrlType) { - switch (dwCtrlType) { - case kNtCtrlCEvent: - return SIGINT; - case kNtCtrlBreakEvent: - return SIGQUIT; - case kNtCtrlCloseEvent: - case kNtCtrlLogoffEvent: // only received by services - case kNtCtrlShutdownEvent: // only received by services - return SIGHUP; - default: - return SIGSTKFLT; - } -} - -__msabi textwindows dontinstrument bool32 __sig_console(uint32_t dwCtrlType) { - struct CosmoTib tls; - __bootstrap_tls(&tls, __builtin_frame_address(0)); - __sig_generate(__sig_console_sig(dwCtrlType), SI_KERNEL); - return true; -} - -// returns 0 if no signal handlers were called, otherwise a bitmask -// consisting of `1` which means a signal handler was invoked which -// didn't have the SA_RESTART flag, and `2`, which means SA_RESTART -// handlers were called (or `3` if both were the case). -textwindows int __sig_check(void) { - int sig; - if ((sig = __sig_get(atomic_load_explicit(&__get_tls()->tib_sigmask, - memory_order_acquire)))) { - return __sig_raise(sig, SI_KERNEL); - } else { - return 0; - } -} - -__attribute__((__constructor__(10))) textstartup void __sig_init(void) { - if (!IsWindows()) - return; - AddVectoredExceptionHandler(true, (void *)__sig_crash); - SetConsoleCtrlHandler((void *)__sig_console, true); -} - -#endif /* __x86_64__ */ diff --git a/libc/calls/sig.internal.h b/libc/calls/sig.internal.h index 48566f303..34b2f1d7c 100644 --- a/libc/calls/sig.internal.h +++ b/libc/calls/sig.internal.h @@ -1,6 +1,8 @@ #ifndef COSMOPOLITAN_LIBC_CALLS_SIGNALS_INTERNAL_H_ #define COSMOPOLITAN_LIBC_CALLS_SIGNALS_INTERNAL_H_ +#include "libc/atomic.h" #include "libc/calls/struct/sigset.h" +#include "libc/nt/thunk/msabi.h" #include "libc/thread/posixthread.internal.h" #define SIG_HANDLED_NO_RESTART 1 @@ -9,8 +11,8 @@ COSMOPOLITAN_C_START_ struct Signals { - _Atomic(uint64_t) pending; - _Atomic(uint64_t) count; + atomic_ulong *process; + atomic_ulong count; }; extern struct Signals __sig; @@ -27,5 +29,8 @@ void __sig_delete(int); void __sig_generate(int, int); void __sig_init(void); +__msabi char16_t *__sig_process_path(char16_t *, uint32_t, int); +__msabi atomic_ulong *__sig_map_process(int, int); + COSMOPOLITAN_C_END_ #endif /* COSMOPOLITAN_LIBC_CALLS_SIGNALS_INTERNAL_H_ */ diff --git a/libc/calls/sigaction.c b/libc/calls/sigaction.c index be67e817c..7c28b6851 100644 --- a/libc/calls/sigaction.c +++ b/libc/calls/sigaction.c @@ -423,7 +423,7 @@ static int __sigaction(int sig, const struct sigaction *act, * } * * void ContinueOnCrash(void) { - * struct sigaction sa = {.sa_handler = OnSigSegv, + * struct sigaction sa = {.sa_sigaction = OnCrash, * .sa_flags = SA_SIGINFO | SA_RESETHAND}; * sigaction(SIGSEGV, &sa, 0); * sigaction(SIGFPE, &sa, 0); diff --git a/libc/calls/sigaltstack.c b/libc/calls/sigaltstack.c index dac5f4526..a580a0fec 100644 --- a/libc/calls/sigaltstack.c +++ b/libc/calls/sigaltstack.c @@ -113,7 +113,7 @@ static int sigaltstack_bsd(const struct sigaltstack *neu, * struct sigaction sa; * struct sigaltstack ss; * ss.ss_flags = 0; - * ss.ss_size = sysconf(_SC_MINSIGSTKSZ) + 8192; + * ss.ss_size = sysconf(_SC_SIGSTKSZ); * ss.ss_sp = malloc(ss.ss_size); * sigaltstack(&ss, 0); * sigemptyset(&sa.ss_mask); @@ -121,11 +121,16 @@ static int sigaltstack_bsd(const struct sigaltstack *neu, * sa.sa_handler = OnStackOverflow; * sigaction(SIGSEGV, &sa, 0); * + * Your stack size should be `sysconf(_SC_SIGSTKSZ)` which should be + * somewhere in the ballpark of 32kb to 64kb. You should go no lower + * than `sysconf(_SC_MINSIGSTKSZ) + 2048` which could be 4kb - 34kb. + * Cosmo also defines `SIGSTKSZ` as 32kb, which should also be safe. + * * @param neu if non-null will install new signal alt stack * @param old if non-null will receive current signal alt stack * @return 0 on success, or -1 w/ errno * @raise EFAULT if bad memory was supplied - * @raise ENOMEM if `neu->ss_size` is less than `MINSIGSTKSZ` + * @raise ENOMEM if `neu->ss_size` is beneath `sysconf(_SC_MINSIGSTKSZ)` */ int sigaltstack(const struct sigaltstack *neu, struct sigaltstack *old) { int rc; diff --git a/libc/calls/sigcheck.c b/libc/calls/sigcheck.c index 74cbcafd1..e8cad756d 100644 --- a/libc/calls/sigcheck.c +++ b/libc/calls/sigcheck.c @@ -23,18 +23,18 @@ #include "libc/sysv/errfuns.h" textwindows int __sigcheck(sigset_t waitmask, bool restartable) { - int sig, handler_was_called; + int sig, handler_was_called = 0; if (_check_cancel() == -1) return -1; - if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) { - handler_was_called = _weaken(__sig_relay)(sig, SI_KERNEL, waitmask); + while (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) { + handler_was_called |= _weaken(__sig_relay)(sig, SI_KERNEL, waitmask); if (_check_cancel() == -1) return -1; - if (handler_was_called & SIG_HANDLED_NO_RESTART) - return eintr(); - if (handler_was_called & SIG_HANDLED_SA_RESTART) - if (!restartable) - return eintr(); } + if (handler_was_called & SIG_HANDLED_NO_RESTART) + return eintr(); + if (handler_was_called & SIG_HANDLED_SA_RESTART) + if (!restartable) + return eintr(); return 0; } diff --git a/libc/calls/sigenter-xnu.c b/libc/calls/sigenter-xnu.c index c68a9c7c5..9d546ff28 100644 --- a/libc/calls/sigenter-xnu.c +++ b/libc/calls/sigenter-xnu.c @@ -33,6 +33,7 @@ #include "libc/runtime/syslib.internal.h" #include "libc/str/str.h" #include "libc/sysv/consts/sa.h" +#include "libc/sysv/consts/sig.h" /** * @fileoverview XNU kernel callback normalization. @@ -513,6 +514,7 @@ privileged void __sigenter_xnu(int sig, struct siginfo_xnu *xnuinfo, flags = __sighandflags[sig]; #ifdef __aarch64__ + // xnu silicon claims to support sa_resethand but it does nothing // this can be tested, since it clears the bit from flags as well if (flags & SA_RESETHAND) { @@ -521,6 +523,13 @@ privileged void __sigenter_xnu(int sig, struct siginfo_xnu *xnuinfo, __sighandflags[sig] = 0; __sighandrvas[sig] = 0; } + + // unlike amd64, the instruction pointer on arm64 isn't advanced + // past the debugger breakpoint instruction automatically. we need + // this so execution can resume after __builtin_trap(). + if (xnuctx && sig == SIGTRAP) + xnuctx->uc_mcontext->__ss.__pc += 4; + #endif if (~flags & SA_SIGINFO) { diff --git a/libc/calls/sigpending.c b/libc/calls/sigpending.c index e11e194c0..0fceafa98 100644 --- a/libc/calls/sigpending.c +++ b/libc/calls/sigpending.c @@ -53,7 +53,7 @@ int sigpending(sigset_t *pending) { } rc = 0; } else if (IsWindows()) { - *pending = atomic_load_explicit(&__sig.pending, memory_order_acquire) | + *pending = atomic_load_explicit(__sig.process, memory_order_acquire) | atomic_load_explicit(&__get_tls()->tib_sigpending, memory_order_acquire); rc = 0; diff --git a/libc/calls/sigsuspend.c b/libc/calls/sigsuspend.c index 134eda44c..fc7187f57 100644 --- a/libc/calls/sigsuspend.c +++ b/libc/calls/sigsuspend.c @@ -21,6 +21,7 @@ #include "libc/calls/sig.internal.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/struct/sigset.internal.h" +#include "libc/calls/struct/timespec.h" #include "libc/dce.h" #include "libc/errno.h" #include "libc/intrin/atomic.h" @@ -53,8 +54,14 @@ int sigsuspend(const sigset_t *ignore) { } else { sigset_t waitmask = ignore ? *ignore : 0; if (IsWindows() || IsMetal()) { - while (!(rc = _park_norestart(-1u, waitmask))) - donothing; + // we don't strictly need to block signals, but it reduces signal + // delivery latency, by preventing other threads from delivering a + // signal asynchronously. it takes about ~5us to deliver a signal + // using SetEvent() whereas it takes ~30us to use SuspendThread(), + // GetThreadContext(), SetThreadContext(), and ResumeThread(). + BLOCK_SIGNALS; + rc = _park_norestart(timespec_max, waitmask); + ALLOW_SIGNALS; } else { rc = sys_sigsuspend((uint64_t[2]){waitmask}, 8); } diff --git a/libc/calls/state.internal.h b/libc/calls/state.internal.h index 003265867..3d4d2a2d9 100644 --- a/libc/calls/state.internal.h +++ b/libc/calls/state.internal.h @@ -13,7 +13,6 @@ extern unsigned __sighandflags[NSIG + 1]; extern uint64_t __sighandmask[NSIG + 1]; extern const struct NtSecurityAttributes kNtIsInheritable; -void __fds_wipe(void); void __fds_lock(void); void __fds_unlock(void); diff --git a/libc/calls/struct/sigset.h b/libc/calls/struct/sigset.h index 3d783c47a..09faa3118 100644 --- a/libc/calls/struct/sigset.h +++ b/libc/calls/struct/sigset.h @@ -4,19 +4,21 @@ COSMOPOLITAN_C_START_ typedef uint64_t sigset_t; -int sigaddset(sigset_t *, int) paramsnonnull(); -int sigdelset(sigset_t *, int) paramsnonnull(); -int sigemptyset(sigset_t *) paramsnonnull(); -int sigfillset(sigset_t *) paramsnonnull(); -int sigandset(sigset_t *, const sigset_t *, const sigset_t *) paramsnonnull(); -int sigorset(sigset_t *, const sigset_t *, const sigset_t *) paramsnonnull(); -int sigisemptyset(const sigset_t *) paramsnonnull() nosideeffect; -int sigismember(const sigset_t *, int) paramsnonnull() nosideeffect; -int sigcountset(const sigset_t *) paramsnonnull() nosideeffect; -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 *); +/* clang-format off */ +int sigaddset(sigset_t *, int) libcesque paramsnonnull(); +int sigdelset(sigset_t *, int) libcesque paramsnonnull(); +int sigemptyset(sigset_t *) libcesque paramsnonnull(); +int sigfillset(sigset_t *) libcesque paramsnonnull(); +int sigandset(sigset_t *, const sigset_t *, const sigset_t *) libcesque paramsnonnull(); +int sigorset(sigset_t *, const sigset_t *, const sigset_t *) libcesque paramsnonnull(); +int sigisemptyset(const sigset_t *) libcesque paramsnonnull() nosideeffect; +int sigismember(const sigset_t *, int) libcesque paramsnonnull() nosideeffect; +int sigcountset(const sigset_t *) libcesque paramsnonnull() nosideeffect; +int sigprocmask(int, const sigset_t *, sigset_t *) dontthrow; +int sigsuspend(const sigset_t *) dontthrow; +int sigpending(sigset_t *) libcesque; +int pthread_sigmask(int, const sigset_t *, sigset_t *) dontthrow; +/* clang-format on */ COSMOPOLITAN_C_END_ #endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGSET_H_ */ diff --git a/libc/calls/struct/sigset.internal.h b/libc/calls/struct/sigset.internal.h index b31093dbb..77af35704 100644 --- a/libc/calls/struct/sigset.internal.h +++ b/libc/calls/struct/sigset.internal.h @@ -5,27 +5,15 @@ #include "libc/sysv/consts/sig.h" COSMOPOLITAN_C_START_ -#ifndef MODE_DBG -/* block sigs because theoretical edge cases */ #define BLOCK_SIGNALS \ do { \ sigset_t _SigMask; \ _SigMask = __sig_block() + #define ALLOW_SIGNALS \ __sig_unblock(_SigMask); \ } \ while (0) -#else -/* doesn't block signals so we can get a crash - report, when a core runtime library crashes */ -#define BLOCK_SIGNALS \ - do { \ - sigset_t _SigMask; \ - sigprocmask(SIG_SETMASK, 0, &_SigMask) -#define ALLOW_SIGNALS \ - } \ - while (0) -#endif sigset_t __sig_block(void); void __sig_unblock(sigset_t); diff --git a/libc/calls/struct/ucontext.internal.h b/libc/calls/struct/ucontext.internal.h index 9122af24a..18a271f10 100644 --- a/libc/calls/struct/ucontext.internal.h +++ b/libc/calls/struct/ucontext.internal.h @@ -1,13 +1,14 @@ #ifndef COSMOPOLITAN_LIBC_CALLS_STRUCT_UCONTEXT_INTERNAL_H_ #define COSMOPOLITAN_LIBC_CALLS_STRUCT_UCONTEXT_INTERNAL_H_ #include "libc/calls/ucontext.h" -#include "libc/nt/struct/context.h" COSMOPOLITAN_C_START_ #ifdef __x86_64__ #define PC rip #define SP rsp #define BP rbp +#define RES0 rax +#define RES1 rdx #define ARG0 rdi #define ARG1 rsi #define ARG2 rdx @@ -18,6 +19,8 @@ COSMOPOLITAN_C_START_ #define PC pc #define SP sp #define BP regs[29] +#define RES0 regs[0] +#define RES1 regs[1] #define ARG0 regs[0] #define ARG1 regs[1] #define ARG2 regs[2] @@ -28,8 +31,5 @@ COSMOPOLITAN_C_START_ #error "unsupported architecture" #endif -void _ntcontext2linux(struct ucontext *, const struct NtContext *); -void _ntlinux2context(struct NtContext *, const ucontext_t *); - COSMOPOLITAN_C_END_ #endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_UCONTEXT_INTERNAL_H_ */ diff --git a/libc/calls/struct/user_regs_struct.h b/libc/calls/struct/user_regs_struct.h deleted file mode 100644 index 5cbe63f5c..000000000 --- a/libc/calls/struct/user_regs_struct.h +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_CALLS_STRUCT_USER_REGS_STRUCT_H_ -#define COSMOPOLITAN_LIBC_CALLS_STRUCT_USER_REGS_STRUCT_H_ -COSMOPOLITAN_C_START_ - -/** - * Linux Kernel user registers. - * - * @note superset of struct pt_regs - * @see ptrace() w/ PTRACE_SYSCALL - */ -struct user_regs_struct { - uint64_t r15; - uint64_t r14; - uint64_t r13; - uint64_t r12; - uint64_t rbp; - uint64_t rbx; - uint64_t r11; - uint64_t r10; - uint64_t r9; - uint64_t r8; - uint64_t rax; - uint64_t rcx; - uint64_t rdx; - uint64_t rsi; - uint64_t rdi; - uint64_t orig_rax; - uint64_t rip; - uint64_t cs; - uint64_t eflags; - uint64_t rsp; - uint64_t ss; - uint64_t fs_base; - uint64_t gs_base; - uint64_t ds; - uint64_t es; - uint64_t fs; - uint64_t gs; -}; - -struct useregs_struct_freebsd { - int64_t r15; - int64_t r14; - int64_t r13; - int64_t r12; - int64_t r11; - int64_t r10; - int64_t r9; - int64_t r8; - int64_t rdi; - int64_t rsi; - int64_t rbp; - int64_t rbx; - int64_t rdx; - int64_t rcx; - int64_t rax; - uint32_t trapno; - uint16_t fs; - uint16_t gs; - uint32_t err; - uint16_t es; - uint16_t ds; - int64_t rip; - int64_t cs; - int64_t rflags; - int64_t rsp; - int64_t ss; -}; - -COSMOPOLITAN_C_END_ -#endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_USER_REGS_STRUCT_H_ */ diff --git a/libc/calls/syscall-nt.internal.h b/libc/calls/syscall-nt.internal.h index 2c4e7dbbf..dafbf18ea 100644 --- a/libc/calls/syscall-nt.internal.h +++ b/libc/calls/syscall-nt.internal.h @@ -2,6 +2,9 @@ #define COSMOPOLITAN_LIBC_CALLS_SYSCALL_NT_INTERNAL_H_ COSMOPOLITAN_C_START_ +extern int sys_getppid_nt_cosmo; +extern int sys_getppid_nt_win32; + bool32 sys_isatty(int); int sys_chdir_nt(const char *); int sys_dup_nt(int, int, int, int); @@ -37,6 +40,7 @@ int sys_unlinkat_nt(int, const char *, int); int64_t sys_lseek_nt(int, int64_t, int); ssize_t sys_read_nt_impl(int, void *, size_t, int64_t); ssize_t sys_readlinkat_nt(int, const char *, char *, size_t); +void sys_getppid_nt_wipe(int, int); COSMOPOLITAN_C_END_ #endif /* COSMOPOLITAN_LIBC_CALLS_SYSCALL_NT_INTERNAL_H_ */ diff --git a/libc/calls/time.c b/libc/calls/time.c index d592bc256..f0455d2b5 100644 --- a/libc/calls/time.c +++ b/libc/calls/time.c @@ -16,10 +16,9 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/time.h" -#include "libc/calls/struct/timeval.h" -#include "libc/dce.h" -#include "libc/sysv/errfuns.h" +#include "libc/calls/calls.h" +#include "libc/calls/struct/timespec.h" +#include "libc/sysv/consts/clock.h" /** * Returns time as seconds from UNIX epoch. @@ -29,15 +28,11 @@ * @asyncsignalsafe */ int64_t time(int64_t *opt_out_ret) { - int64_t secs; - struct timeval tv; - if (gettimeofday(&tv, 0) != -1) { - secs = tv.tv_sec; - if (opt_out_ret) { - *opt_out_ret = secs; - } - } else { - secs = -1; - } + int64_t secs = -1; + struct timespec ts; + if (!clock_gettime(CLOCK_REALTIME, &ts)) + secs = ts.tv_sec; + if (opt_out_ret) + *opt_out_ret = secs; return secs; } diff --git a/libc/calls/usleep.c b/libc/calls/usleep.c index 82dd7b55f..b137bfdd1 100644 --- a/libc/calls/usleep.c +++ b/libc/calls/usleep.c @@ -34,10 +34,14 @@ * @norestart */ int usleep(uint64_t micros) { - errno_t err; - struct timespec ts = timespec_frommicros(micros); - err = clock_nanosleep(CLOCK_REALTIME, 0, &ts, 0); - if (err) - return errno = err, -1; + // All OSes except OpenBSD return instantly on usleep(0). So we might + // as well avoid system call overhead and helping OpenBSD work better + if (micros) { + errno_t err; + struct timespec ts = timespec_frommicros(micros); + err = clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, 0); + if (err) + return errno = err, -1; + } return 0; } diff --git a/libc/calls/winexec.c b/libc/calls/winexec.c index ca52372c5..429589c10 100644 --- a/libc/calls/winexec.c +++ b/libc/calls/winexec.c @@ -80,7 +80,8 @@ textwindows int IsWindowsExecutable(int64_t handle, const char16_t *path) { uint32_t got; BLOCK_SIGNALS; struct NtOverlapped overlap = {.hEvent = CreateEvent(0, 0, 0, 0)}; - ok = (ReadFile(handle, buf, 2, 0, &overlap) || + ok = overlap.hEvent && + (ReadFile(handle, buf, 2, 0, &overlap) || GetLastError() == kNtErrorIoPending) && GetOverlappedResult(handle, &overlap, &got, true); CloseHandle(overlap.hEvent); diff --git a/libc/cosmo.h b/libc/cosmo.h index 1080c97d7..e91621e48 100644 --- a/libc/cosmo.h +++ b/libc/cosmo.h @@ -1,5 +1,6 @@ #ifndef COSMOPOLITAN_LIBC_COSMO_H_ #define COSMOPOLITAN_LIBC_COSMO_H_ +#include "libc/calls/struct/timespec.h" COSMOPOLITAN_C_START_ #ifndef __cplusplus @@ -8,14 +9,33 @@ COSMOPOLITAN_C_START_ #define _COSMO_ATOMIC(x) x #endif -errno_t cosmo_once(_COSMO_ATOMIC(unsigned) *, void (*)(void)) libcesque; +errno_t cosmo_once(_COSMO_ATOMIC(unsigned) *, void (*)(void)); int systemvpe(const char *, char *const[], char *const[]) libcesque; char *GetProgramExecutableName(void) libcesque; void unleaf(void) libcesque; +bool32 IsLinuxModern(void) libcesque; + int __demangle(char *, const char *, size_t) libcesque; int __is_mangled(const char *) libcesque; -bool32 IsLinuxModern(void) libcesque; + +int cosmo_args(const char *, char ***) libcesque; int LoadZipArgs(int *, char ***) libcesque; +int cosmo_futex_wake(_COSMO_ATOMIC(int) *, int, char); +int cosmo_futex_wait(_COSMO_ATOMIC(int) *, int, char, int, + const struct timespec *); + +errno_t cosmo_stack_alloc(size_t *, size_t *, void **) libcesque; +errno_t cosmo_stack_free(void *, size_t, size_t) libcesque; +void cosmo_stack_clear(void) libcesque; +void cosmo_stack_setmaxstacks(int) libcesque; +int cosmo_stack_getmaxstacks(void) libcesque; + +int __deadlock_check(void *, int) libcesque; +int __deadlock_tracked(void *) libcesque; +void __deadlock_record(void *, int) libcesque; +void __deadlock_track(void *, int) libcesque; +void __deadlock_untrack(void *) libcesque; + COSMOPOLITAN_C_END_ #endif /* COSMOPOLITAN_LIBC_COSMO_H_ */ diff --git a/libc/crt/crt.S b/libc/crt/crt.S index d34bc83b8..74226c641 100644 --- a/libc/crt/crt.S +++ b/libc/crt/crt.S @@ -47,7 +47,14 @@ __oops_win32: // @note ape.S and ape-loader both set RCX to XNU on Darwin // @noreturn _start: -#ifdef __x86_64__ + .cfi_startproc +#if defined(__x86_64__) + .cfi_undefined rip +#elif defined(__aarch64__) + .cfi_undefined x30 +#endif /* __x86_64__ */ + +#if defined(__x86_64__) #if SupportsFreebsd() // detect free besiyata dishmaya @@ -159,4 +166,5 @@ _start: #else #error "architecture unsupported" #endif /* __x86_64__ */ + .cfi_endproc .endfn _start,weak,hidden diff --git a/libc/dlopen/dlopen.c b/libc/dlopen/dlopen.c index 3f56cff8c..2a47aa99e 100644 --- a/libc/dlopen/dlopen.c +++ b/libc/dlopen/dlopen.c @@ -57,6 +57,7 @@ #include "libc/sysv/consts/prot.h" #include "libc/sysv/errfuns.h" #include "libc/temp.h" +#include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" #include "libc/thread/tls.h" @@ -131,6 +132,8 @@ struct { long __sysv2nt14(); long foreign_tramp(); +void __dlopen_lock(void); +void __dlopen_unlock(void); static _Thread_local char dlerror_buf[128]; @@ -251,7 +254,7 @@ static bool elf_slurp(struct Loaded *l, int fd, const char *file) { return true; } -static dontinline bool elf_load(struct Loaded *l, const char *file, long pagesz, +dontinline static bool elf_load(struct Loaded *l, const char *file, long pagesz, char *interp_path, size_t interp_size) { int fd; if ((fd = open(file, O_RDONLY | O_CLOEXEC)) == -1) @@ -277,7 +280,7 @@ static long *push_strs(long *sp, char **list, int count) { return sp; } -static wontreturn dontinstrument void foreign_helper(void **p) { +wontreturn dontinstrument static void foreign_helper(void **p) { __foreign.dlopen = p[0]; __foreign.dlsym = p[1]; __foreign.dlclose = p[2]; @@ -285,7 +288,7 @@ static wontreturn dontinstrument void foreign_helper(void **p) { _longjmp(__foreign.jb, 1); } -static dontinline void elf_exec(const char *file, char **envp) { +dontinline static void elf_exec(const char *file, char **envp) { // get microprocessor page size long pagesz = __pagesize; @@ -409,7 +412,7 @@ static char *dlerror_set(const char *str) { return dlerror_buf; } -static dontinline char *foreign_alloc_block(void) { +dontinline static char *foreign_alloc_block(void) { char *p = 0; size_t sz = 65536; if (!IsWindows()) { @@ -432,17 +435,16 @@ static dontinline char *foreign_alloc_block(void) { return p; } -static dontinline void *foreign_alloc(size_t n) { +dontinline static void *foreign_alloc(size_t n) { void *res; static char *block; - static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; - pthread_mutex_lock(&lock); + __dlopen_lock(); if (!block || READ32LE(block) + n > 65536) if (!(block = foreign_alloc_block())) return 0; res = block + READ32LE(block); WRITE32LE(block, READ32LE(block) + n); - pthread_mutex_unlock(&lock); + __dlopen_unlock(); return res; } @@ -546,7 +548,7 @@ static void *foreign_thunk_nt(void *func) { return code; } -static dontinline bool foreign_compile(char exe[hasatleast PATH_MAX]) { +dontinline static bool foreign_compile(char exe[hasatleast PATH_MAX]) { // construct path strlcpy(exe, get_tmp_dir(), PATH_MAX); @@ -808,7 +810,7 @@ void *cosmo_dlopen(const char *path, int mode) { } ALLOW_CANCELATION; ALLOW_SIGNALS; - STRACE("dlopen(%#s, %d) → %p% m", path, mode, res); + STRACE("cosmo_dlopen(%#s, %d) → %p% m", path, mode, res); return res; } @@ -853,7 +855,7 @@ void *cosmo_dlsym(void *handle, const char *name) { } else { func = 0; } - STRACE("dlsym(%p, %#s) → %p", handle, name, func); + STRACE("cosmo_dlsym(%p, %#s) → %p", handle, name, func); return func; } @@ -888,7 +890,7 @@ int cosmo_dlclose(void *handle) { } else { res = -1; } - STRACE("dlclose(%p) → %d", handle, res); + STRACE("cosmo_dlclose(%p) → %d", handle, res); return res; } @@ -907,6 +909,6 @@ char *cosmo_dlerror(void) { } else { res = dlerror_buf; } - STRACE("dlerror() → %#s", res); + STRACE("cosmo_dlerror() → %#s", res); return res; } diff --git a/libc/dlopen/stubs.c b/libc/dlopen/stubs.c index 8dad1af05..357f864f3 100644 --- a/libc/dlopen/stubs.c +++ b/libc/dlopen/stubs.c @@ -17,6 +17,10 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/dlopen/dlfcn.h" +#include "libc/intrin/strace.h" + +#define DLOPEN_ERROR \ + "dlopen() isn't supported; consider using cosmo_dlopen() and read its docs" /** * Opens dynamic shared object using host platform libc. @@ -27,12 +31,13 @@ * * @return null always */ -void *dlopen(const char *, int) { +void *dlopen(const char *path, int mode) { + STRACE("dlopen(%#s, %d) → 0 [%s]", path, mode, DLOPEN_ERROR); return 0; } char *dlerror(void) { - return "dlopen() isn't supported by cosmo; try using cosmo_dlopen()"; + return DLOPEN_ERROR; } void *dlsym(void *, const char *) { diff --git a/libc/elf/def.h b/libc/elf/def.h index 913e9c930..04d69985e 100644 --- a/libc/elf/def.h +++ b/libc/elf/def.h @@ -68,6 +68,7 @@ #define EM_NONE 0 #define EM_M32 1 #define EM_386 3 +#define EM_MIPS 8 #define EM_PPC64 21 #define EM_S390 22 #define EM_ARM 40 diff --git a/libc/errno.h b/libc/errno.h index 8a3a04f30..f8963ed98 100644 --- a/libc/errno.h +++ b/libc/errno.h @@ -26,11 +26,11 @@ COSMOPOLITAN_C_START_ /* this header is included by 700+ files; therefore we */ /* hand-roll &__get_tls()->tib_errno to avoid #include */ /* cosmopolitan uses x28 as the tls register b/c apple */ -#define errno \ - (*__extension__({ \ - errno_t *__ep; \ - __asm__("sub\t%0,x28,#512-0x3c" : "=r"(__ep)); \ - __ep; \ +#define errno \ + (*__extension__({ \ + errno_t *__ep; \ + __asm__("sub\t%0,x28,#1024-0x3c" : "=r"(__ep)); \ + __ep; \ })) #else #define errno (*__errno_location()) diff --git a/libc/fmt/internal.h b/libc/fmt/internal.h index 8203bf153..b82c4b382 100644 --- a/libc/fmt/internal.h +++ b/libc/fmt/internal.h @@ -2,6 +2,7 @@ #define COSMOPOLITAN_LIBC_FMT_STRTOL_H_ #include "libc/ctype.h" #include "libc/errno.h" +#include "libc/nt/thunk/msabi.h" #include "libc/str/str.h" #define CONSUME_SPACES(t, s, c) \ @@ -48,5 +49,6 @@ int __vcscanf(int (*)(void *), int (*)(int, void *), void *, const char *, va_list); int __fmt(void *, void *, const char *, va_list, int *); +char16_t *__itoa16(char16_t[21], uint64_t) __msabi; #endif /* COSMOPOLITAN_LIBC_FMT_STRTOL_H_ */ diff --git a/libc/integral/c.inc b/libc/integral/c.inc index 04aeb2229..7a00cd8da 100644 --- a/libc/integral/c.inc +++ b/libc/integral/c.inc @@ -135,7 +135,7 @@ typedef struct { #define strftimeesque(n) __attribute__((__format__(__strftime__, n, 0))) #ifndef privileged -#define privileged _Section(".privileged") dontinline dontinstrument dontubsan +#define privileged _Section(".privileged") dontinstrument dontubsan #endif #ifndef wontreturn diff --git a/libc/integral/normalize.inc b/libc/integral/normalize.inc index 4343d782a..97bd665cb 100644 --- a/libc/integral/normalize.inc +++ b/libc/integral/normalize.inc @@ -2,9 +2,9 @@ #undef __COSMOPOLITAN__ #endif -#define __COSMOPOLITAN_MAJOR__ 3 -#define __COSMOPOLITAN_MINOR__ 9 -#define __COSMOPOLITAN_PATCH__ 1 +#define __COSMOPOLITAN_MAJOR__ 4 +#define __COSMOPOLITAN_MINOR__ 0 +#define __COSMOPOLITAN_PATCH__ 2 #define __COSMOPOLITAN__ \ (100000000 * __COSMOPOLITAN_MAJOR__ + 1000000 * __COSMOPOLITAN_MINOR__ + \ __COSMOPOLITAN_PATCH__) diff --git a/libc/intrin/BUILD.mk b/libc/intrin/BUILD.mk index abe5b17b9..c84aeebaf 100644 --- a/libc/intrin/BUILD.mk +++ b/libc/intrin/BUILD.mk @@ -30,9 +30,11 @@ LIBC_INTRIN_A_CHECKS = \ LIBC_INTRIN_A_DIRECTDEPS = \ LIBC_NEXGEN32E \ LIBC_NT_KERNEL32 \ + LIBC_NT_REALTIME \ + LIBC_NT_SYNCHRONIZATION \ LIBC_NT_WS2_32 \ LIBC_SYSV \ - LIBC_SYSV_CALLS + LIBC_SYSV_CALLS \ LIBC_INTRIN_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_INTRIN_A_DIRECTDEPS),$($(x)))) @@ -106,14 +108,30 @@ o//libc/intrin/demangle.o: private \ CFLAGS += \ -mgeneral-regs-only +# ensure that division is optimized +o/$(MODE)/libc/intrin/windowsdurationtotimeval.o \ +o/$(MODE)/libc/intrin/windowsdurationtotimespec.o \ +o/$(MODE)/libc/intrin/timevaltowindowstime.o \ +o/$(MODE)/libc/intrin/timespectowindowstime.o \ +o/$(MODE)/libc/intrin/windowstimetotimeval.o \ +o/$(MODE)/libc/intrin/windowstimetotimespec.o: private \ + CFLAGS += \ + -O2 + # these assembly files are safe to build on aarch64 +o/$(MODE)/libc/intrin/getcontext.o: libc/intrin/getcontext.S + @$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -c $< +o/$(MODE)/libc/intrin/swapcontext.o: libc/intrin/swapcontext.S + @$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -c $< +o/$(MODE)/libc/intrin/tailcontext.o: libc/intrin/tailcontext.S + @$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -c $< o/$(MODE)/libc/intrin/aarch64/%.o: libc/intrin/aarch64/%.S @$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -c $< o/$(MODE)/libc/intrin/fenv.o: libc/intrin/fenv.S @$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -c $< o/$(MODE)/libc/intrin/gcov.o: libc/intrin/gcov.S @$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -c $< -o/$(MODE)/libc/intrin/futex.o: libc/intrin/futex.S +o/$(MODE)/libc/intrin/cosmo_futex_thunk.o: libc/intrin/cosmo_futex_thunk.S @$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -c $< o/$(MODE)/libc/intrin/typeinfo.o: libc/intrin/typeinfo.S @$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -c $< diff --git a/libc/intrin/__getenv.c b/libc/intrin/__getenv.c index b387b458d..6d40aa91d 100644 --- a/libc/intrin/__getenv.c +++ b/libc/intrin/__getenv.c @@ -20,7 +20,7 @@ #include "libc/intrin/getenv.h" #include "libc/intrin/kprintf.h" -privileged struct Env __getenv(char **p, const char *k) { +privileged optimizesize struct Env __getenv(char **p, const char *k) { char *t; int i, j; for (i = 0; (t = p[i]); ++i) { diff --git a/libc/intrin/bzero.c b/libc/intrin/bzero.c index 8f5087109..2d51a9314 100644 --- a/libc/intrin/bzero.c +++ b/libc/intrin/bzero.c @@ -16,155 +16,18 @@ │ 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/nexgen32e/nexgen32e.h" -#include "libc/nexgen32e/x86feature.h" #include "libc/str/str.h" -typedef char xmm_t __attribute__((__vector_size__(16), __aligned__(1))); -typedef long long xmm_a __attribute__((__vector_size__(16), __aligned__(16))); - -static void bzero128(char *p, size_t n) { - xmm_t v = {0}; - if (n <= 32) { - *(xmm_t *)(p + n - 16) = v; - *(xmm_t *)p = v; - } else { - do { - n -= 32; - *(xmm_t *)(p + n) = v; - *(xmm_t *)(p + n + 16) = v; - } while (n > 32); - *(xmm_t *)(p + 16) = v; - *(xmm_t *)p = v; - } -} - -#if defined(__x86_64__) && !defined(__chibicc__) -_Microarchitecture("avx") static void bzero_avx(char *p, size_t n) { - xmm_t v = {0}; - if (n <= 32) { - *(xmm_t *)(p + n - 16) = v; - *(xmm_t *)p = v; - } else if (n >= 1024 && X86_HAVE(ERMS)) { - asm("rep stosb" : "+D"(p), "+c"(n), "=m"(*(char(*)[n])p) : "a"(0)); - } else { - if (n < kHalfCache3 || !kHalfCache3) { - do { - n -= 32; - *(xmm_t *)(p + n) = v; - *(xmm_t *)(p + n + 16) = v; - } while (n > 32); - } else { - while ((uintptr_t)(p + n) & 15) { - p[--n] = 0; - } - do { - n -= 32; - __builtin_ia32_movntdq((xmm_a *)(p + n), (xmm_a)v); - __builtin_ia32_movntdq((xmm_a *)(p + n + 16), (xmm_a)v); - } while (n > 32); - asm("sfence"); - } - *(xmm_t *)(p + 16) = v; - *(xmm_t *)p = v; - } -} -#endif - /** * Sets memory to zero. * - * bzero n=0 661 picoseconds - * bzero n=1 661 ps/byte 1,476 mb/s - * bzero n=2 330 ps/byte 2,952 mb/s - * bzero n=3 220 ps/byte 4,428 mb/s - * bzero n=4 165 ps/byte 5,904 mb/s - * bzero n=7 94 ps/byte 10,333 mb/s - * bzero n=8 41 ps/byte 23,618 mb/s - * bzero n=15 44 ps/byte 22,142 mb/s - * bzero n=16 20 ps/byte 47,236 mb/s - * bzero n=31 21 ps/byte 45,760 mb/s - * bzero n=32 20 ps/byte 47,236 mb/s - * bzero n=63 10 ps/byte 92,997 mb/s - * bzero n=64 15 ps/byte 62,982 mb/s - * bzero n=127 15 ps/byte 62,490 mb/s - * bzero n=128 10 ps/byte 94,473 mb/s - * bzero n=255 14 ps/byte 68,439 mb/s - * bzero n=256 9 ps/byte 105 gb/s - * bzero n=511 15 ps/byte 62,859 mb/s - * bzero n=512 11 ps/byte 83,976 mb/s - * bzero n=1023 15 ps/byte 61,636 mb/s - * bzero n=1024 10 ps/byte 88,916 mb/s - * bzero n=2047 9 ps/byte 105 gb/s - * bzero n=2048 8 ps/byte 109 gb/s - * bzero n=4095 8 ps/byte 115 gb/s - * bzero n=4096 8 ps/byte 118 gb/s - * bzero n=8191 7 ps/byte 129 gb/s - * bzero n=8192 7 ps/byte 130 gb/s - * bzero n=16383 6 ps/byte 136 gb/s - * bzero n=16384 6 ps/byte 137 gb/s - * bzero n=32767 6 ps/byte 140 gb/s - * bzero n=32768 6 ps/byte 141 gb/s - * bzero n=65535 15 ps/byte 64,257 mb/s - * bzero n=65536 15 ps/byte 64,279 mb/s - * bzero n=131071 15 ps/byte 63,166 mb/s - * bzero n=131072 15 ps/byte 63,115 mb/s - * bzero n=262143 15 ps/byte 62,052 mb/s - * bzero n=262144 15 ps/byte 62,097 mb/s - * bzero n=524287 15 ps/byte 61,699 mb/s - * bzero n=524288 15 ps/byte 61,674 mb/s - * bzero n=1048575 16 ps/byte 60,179 mb/s - * bzero n=1048576 15 ps/byte 61,330 mb/s - * bzero n=2097151 15 ps/byte 61,071 mb/s - * bzero n=2097152 15 ps/byte 61,065 mb/s - * bzero n=4194303 16 ps/byte 60,942 mb/s - * bzero n=4194304 16 ps/byte 60,947 mb/s - * bzero n=8388607 16 ps/byte 60,872 mb/s - * bzero n=8388608 16 ps/byte 60,879 mb/s - * * @param p is memory address * @param n is byte length * @return p * @asyncsignalsafe */ void bzero(void *p, size_t n) { - char *b; - uint64_t x; - b = p; -#ifdef __x86_64__ - asm("xorl\t%k0,%k0" : "=r"(x)); -#else - if (1) { - memset(p, 0, n); - return; - } - x = 0; -#endif - if (n <= 16) { - if (n >= 8) { - __builtin_memcpy(b, &x, 8); - __builtin_memcpy(b + n - 8, &x, 8); - } else if (n >= 4) { - __builtin_memcpy(b, &x, 4); - __builtin_memcpy(b + n - 4, &x, 4); - } else if (n) { - do { - asm volatile("" ::: "memory"); - b[--n] = x; - } while (n); - } -#if defined(__x86_64__) && !defined(__chibicc__) - } else if (IsTiny()) { - asm("rep stosb" : "+D"(b), "+c"(n), "=m"(*(char(*)[n])b) : "a"(0)); - return; - } else if (X86_HAVE(AVX)) { - bzero_avx(b, n); -#endif - } else { - bzero128(b, n); - } + memset(p, 0, n); } __weak_reference(bzero, explicit_bzero); diff --git a/libc/calls/checkcancel.c b/libc/intrin/checkcancel.c similarity index 100% rename from libc/calls/checkcancel.c rename to libc/intrin/checkcancel.c diff --git a/libc/calls/clock_gettime-mono.c b/libc/intrin/clock_gettime-mono.c similarity index 100% rename from libc/calls/clock_gettime-mono.c rename to libc/intrin/clock_gettime-mono.c diff --git a/libc/calls/clock_gettime-nt.c b/libc/intrin/clock_gettime-nt.c similarity index 97% rename from libc/calls/clock_gettime-nt.c rename to libc/intrin/clock_gettime-nt.c index 911223cb7..9020e9cfd 100644 --- a/libc/calls/clock_gettime-nt.c +++ b/libc/intrin/clock_gettime-nt.c @@ -59,7 +59,7 @@ textwindows int sys_clock_gettime_nt(int clock, struct timespec *ts) { // —Quoth MSDN § Windows Time // QueryUnbiasedInterruptTimePrecise(&hectons); - *ts = timespec_fromnanos(hectons * 100); + *ts = WindowsDurationToTimeSpec(hectons); return 0; case _CLOCK_MONOTONIC_COARSE: // @@ -83,7 +83,7 @@ textwindows int sys_clock_gettime_nt(int clock, struct timespec *ts) { // —Quoth MSDN § QueryUnbiasedInterruptTimePrecise // QueryUnbiasedInterruptTime(&hectons); - *ts = timespec_fromnanos(hectons * 100); + *ts = WindowsDurationToTimeSpec(hectons); return 0; case _CLOCK_BOOTTIME: // @@ -95,7 +95,7 @@ textwindows int sys_clock_gettime_nt(int clock, struct timespec *ts) { // —Quoth MSDN § Interrupt Time // QueryInterruptTimePrecise(&hectons); - *ts = timespec_fromnanos(hectons * 100); + *ts = WindowsDurationToTimeSpec(hectons); return 0; case _CLOCK_PROCESS_CPUTIME_ID: GetProcessTimes(GetCurrentProcess(), &ftCreation, &ftExit, &ftKernel, diff --git a/libc/calls/clock_gettime-sysv.c b/libc/intrin/clock_gettime-sysv.c similarity index 100% rename from libc/calls/clock_gettime-sysv.c rename to libc/intrin/clock_gettime-sysv.c diff --git a/libc/calls/clock_gettime-xnu.c b/libc/intrin/clock_gettime-xnu.c similarity index 100% rename from libc/calls/clock_gettime-xnu.c rename to libc/intrin/clock_gettime-xnu.c diff --git a/libc/calls/clock_gettime.c b/libc/intrin/clock_gettime.c similarity index 100% rename from libc/calls/clock_gettime.c rename to libc/intrin/clock_gettime.c diff --git a/libc/calls/clock_gettime_monotonic_nt.c b/libc/intrin/clock_gettime_monotonic_nt.c similarity index 100% rename from libc/calls/clock_gettime_monotonic_nt.c rename to libc/intrin/clock_gettime_monotonic_nt.c diff --git a/third_party/nsync/futex.c b/libc/intrin/cosmo_futex.c similarity index 71% rename from third_party/nsync/futex.c rename to libc/intrin/cosmo_futex.c index dc550276c..0c0531894 100644 --- a/third_party/nsync/futex.c +++ b/libc/intrin/cosmo_futex.c @@ -16,18 +16,15 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/sysv/consts/futex.h" #include "libc/assert.h" #include "libc/atomic.h" -#include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/sig.internal.h" -#include "libc/calls/state.internal.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/struct/sigset.internal.h" #include "libc/calls/struct/timespec.h" #include "libc/calls/struct/timespec.internal.h" -#include "libc/calls/syscall_support-nt.internal.h" #include "libc/cosmo.h" #include "libc/dce.h" #include "libc/errno.h" @@ -37,62 +34,56 @@ #include "libc/intrin/ulock.h" #include "libc/intrin/weaken.h" #include "libc/limits.h" -#include "libc/nexgen32e/vendor.internal.h" #include "libc/nt/runtime.h" #include "libc/nt/synchronization.h" -#include "libc/runtime/clktck.h" +#include "libc/sysv/consts/clock.h" +#include "libc/sysv/consts/futex.h" #include "libc/sysv/consts/sicode.h" -#include "libc/sysv/consts/timer.h" #include "libc/sysv/errfuns.h" #include "libc/thread/freebsd.internal.h" #include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" -#include "libc/thread/tls.h" -#include "third_party/nsync/atomic.h" -#include "third_party/nsync/time.h" -#include "third_party/nsync/common.internal.h" -#include "third_party/nsync/futex.internal.h" -#include "third_party/nsync/time.h" +// clang-format off #define FUTEX_WAIT_BITS_ FUTEX_BITSET_MATCH_ANY -errno_t _futex (atomic_int *, int, int, const struct timespec *, int *, int); -errno_t _futex_wake (atomic_int *, int, int) asm ("_futex"); +errno_t cosmo_futex_thunk (atomic_int *, int, int, const struct timespec *, int *, int); +errno_t _futex_wake (atomic_int *, int, int) asm ("cosmo_futex_thunk"); int sys_futex_cp (atomic_int *, int, int, const struct timespec *, int *, int); -static struct NsyncFutex { +static struct CosmoFutex { atomic_uint once; int FUTEX_WAIT_; int FUTEX_PRIVATE_FLAG_; int FUTEX_CLOCK_REALTIME_; bool is_supported; bool timeout_is_relative; -} nsync_futex_; +} g_cosmo_futex; -static void nsync_futex_init_ (void) { +static void cosmo_futex_init (void) { int e; atomic_int x; - nsync_futex_.FUTEX_WAIT_ = FUTEX_WAIT; + g_cosmo_futex.FUTEX_WAIT_ = FUTEX_WAIT; if (IsWindows ()) { - nsync_futex_.is_supported = true; + g_cosmo_futex.is_supported = true; return; } if (IsXnu ()) { - nsync_futex_.is_supported = true; - nsync_futex_.timeout_is_relative = true; + g_cosmo_futex.is_supported = true; + g_cosmo_futex.timeout_is_relative = true; return; } if (IsFreebsd ()) { - nsync_futex_.is_supported = true; - nsync_futex_.FUTEX_PRIVATE_FLAG_ = FUTEX_PRIVATE_FLAG; + g_cosmo_futex.is_supported = true; + g_cosmo_futex.FUTEX_PRIVATE_FLAG_ = FUTEX_PRIVATE_FLAG; return; } - if (!(nsync_futex_.is_supported = IsLinux () || IsOpenbsd ())) + if (!(g_cosmo_futex.is_supported = IsLinux () || IsOpenbsd ())) return; // In our testing, we found that the monotonic clock on various @@ -100,7 +91,7 @@ static void nsync_futex_init_ (void) { // better behaved than the realtime clock, and routinely took // large steps backwards, especially on multiprocessors. Given // that "monotonic" doesn't seem to mean what it says, - // implementers of nsync_time might consider retaining the + // implementers of cosmo_time might consider retaining the // simplicity of a single epoch within an address space, by // configuring any time synchronization mechanism (like ntp) to // adjust for leap seconds by adjusting the rate, rather than @@ -108,31 +99,32 @@ static void nsync_futex_init_ (void) { e = errno; atomic_store_explicit (&x, 0, memory_order_relaxed); if (IsLinux () && - _futex (&x, FUTEX_WAIT_BITSET | FUTEX_CLOCK_REALTIME, - 1, 0, 0, FUTEX_BITSET_MATCH_ANY) == -EAGAIN) { - nsync_futex_.FUTEX_WAIT_ = FUTEX_WAIT_BITSET; - nsync_futex_.FUTEX_PRIVATE_FLAG_ = FUTEX_PRIVATE_FLAG; - nsync_futex_.FUTEX_CLOCK_REALTIME_ = FUTEX_CLOCK_REALTIME; + cosmo_futex_thunk (&x, FUTEX_WAIT_BITSET | FUTEX_CLOCK_REALTIME, + 1, 0, 0, FUTEX_BITSET_MATCH_ANY) == -EAGAIN) { + g_cosmo_futex.FUTEX_WAIT_ = FUTEX_WAIT_BITSET; + g_cosmo_futex.FUTEX_PRIVATE_FLAG_ = FUTEX_PRIVATE_FLAG; + g_cosmo_futex.FUTEX_CLOCK_REALTIME_ = FUTEX_CLOCK_REALTIME; } else if (IsOpenbsd () || (IsLinux () && !_futex_wake (&x, FUTEX_WAKE_PRIVATE, 1))) { - nsync_futex_.FUTEX_WAIT_ = FUTEX_WAIT; - nsync_futex_.FUTEX_PRIVATE_FLAG_ = FUTEX_PRIVATE_FLAG; - nsync_futex_.timeout_is_relative = true; + g_cosmo_futex.FUTEX_WAIT_ = FUTEX_WAIT; + g_cosmo_futex.FUTEX_PRIVATE_FLAG_ = FUTEX_PRIVATE_FLAG; + g_cosmo_futex.timeout_is_relative = true; } else { - nsync_futex_.FUTEX_WAIT_ = FUTEX_WAIT; - nsync_futex_.timeout_is_relative = true; + g_cosmo_futex.FUTEX_WAIT_ = FUTEX_WAIT; + g_cosmo_futex.timeout_is_relative = true; } errno = e; } -static uint32_t nsync_time_64to32u (uint64_t duration) { +static uint32_t cosmo_time_64to32u (uint64_t duration) { if (duration <= -1u) return duration; return -1u; } -static int nsync_futex_polyfill_ (atomic_int *w, int expect, int clock, struct timespec *abstime) { +static int cosmo_futex_polyfill (atomic_int *w, int expect, int clock, + struct timespec *abstime) { for (;;) { if (atomic_load_explicit (w, memory_order_acquire) != expect) return 0; @@ -148,10 +140,10 @@ static int nsync_futex_polyfill_ (atomic_int *w, int expect, int clock, struct t } } -static int nsync_futex_wait_win32_ (atomic_int *w, int expect, char pshare, - int clock, const struct timespec *timeout, - struct PosixThread *pt, - sigset_t waitmask) { +static int cosmo_futex_wait_win32 (atomic_int *w, int expect, char pshare, + int clock, const struct timespec *timeout, + struct PosixThread *pt, + sigset_t waitmask) { #ifdef __x86_64__ int sig; bool32 ok; @@ -183,9 +175,9 @@ static int nsync_futex_wait_win32_ (atomic_int *w, int expect, char pshare, pt->pt_blkmask = waitmask; atomic_store_explicit (&pt->pt_blocker, w, memory_order_release); } - ok = WaitOnAddress (w, &expect, sizeof(int), nsync_time_64to32u (timespec_tomillis (wait))); + ok = WaitOnAddress (w, &expect, sizeof(int), cosmo_time_64to32u (timespec_tomillis (wait))); if (pt) { - /* __sig_cancel wakes our futex without changing `w` after enqueing signals */ + /* __sig_wake wakes our futex without changing `w` after enqueing signals */ atomic_store_explicit (&pt->pt_blocker, 0, memory_order_release); if (ok && atomic_load_explicit (w, memory_order_acquire) == expect && (sig = __sig_get (waitmask))) { __sig_relay (sig, SI_KERNEL, waitmask); @@ -197,7 +189,7 @@ static int nsync_futex_wait_win32_ (atomic_int *w, int expect, char pshare, if (ok) { return 0; } else { - ASSERT (GetLastError () == ETIMEDOUT); + unassert (GetLastError () == ETIMEDOUT); } } #else @@ -205,14 +197,14 @@ static int nsync_futex_wait_win32_ (atomic_int *w, int expect, char pshare, #endif /* __x86_64__ */ } -static int nsync_futex_fix_timeout_ (struct timespec *memory, int clock, - const struct timespec *abstime, - struct timespec **result) { +static int cosmo_futex_fix_timeout (struct timespec *memory, int clock, + const struct timespec *abstime, + struct timespec **result) { struct timespec now; if (!abstime) { *result = 0; return 0; - } else if (!nsync_futex_.timeout_is_relative) { + } else if (!g_cosmo_futex.timeout_is_relative) { *memory = *abstime; *result = memory; return 0; @@ -225,22 +217,41 @@ static int nsync_futex_fix_timeout_ (struct timespec *memory, int clock, } } -int nsync_futex_wait_ (atomic_int *w, int expect, char pshare, - int clock, const struct timespec *abstime) { +/** + * Waits on futex. + * + * This function may be used to ask the OS to park the calling thread + * until cosmo_futex_wake() is called on the memory address `w`. + * + * @param w is your futex + * @param expect is the value `*w` is expected to have on entry + * @param pshare is `PTHREAD_PROCESS_PRIVATE` / `PTHREAD_PROCESS_SHARED` + * @param clock is `CLOCK_MONOTONIC`, `CLOCK_REALTIME`, etc. + * @param abstime is null to wait forever or absolute timestamp to stop + * @return 0 on success, or -errno on error + * @raise EINVAL on bad parameter + * @raise EAGAIN if `*w` wasn't `expect` + * @raise EINTR if a signal handler was called while waiting + * @raise ECANCELED if calling thread was canceled while waiting + * @cancelationpoint + */ +int cosmo_futex_wait (atomic_int *w, int expect, char pshare, + int clock, const struct timespec *abstime) { int e, rc, op; struct CosmoTib *tib; struct PosixThread *pt; struct timespec tsmem; struct timespec *timeout = 0; + BEGIN_CANCELATION_POINT; - cosmo_once (&nsync_futex_.once, nsync_futex_init_); + cosmo_once (&g_cosmo_futex.once, cosmo_futex_init); - op = nsync_futex_.FUTEX_WAIT_; + op = g_cosmo_futex.FUTEX_WAIT_; if (pshare == PTHREAD_PROCESS_PRIVATE) - op |= nsync_futex_.FUTEX_PRIVATE_FLAG_; + op |= g_cosmo_futex.FUTEX_PRIVATE_FLAG_; if (clock == CLOCK_REALTIME || clock == CLOCK_REALTIME_COARSE) - op |= nsync_futex_.FUTEX_CLOCK_REALTIME_; + op |= g_cosmo_futex.FUTEX_CLOCK_REALTIME_; if (abstime && timespec_cmp (*abstime, timespec_zero) <= 0) { rc = -ETIMEDOUT; @@ -252,7 +263,7 @@ int nsync_futex_wait_ (atomic_int *w, int expect, char pshare, goto Finished; } - if ((rc = nsync_futex_fix_timeout_ (&tsmem, clock, abstime, &timeout))) + if ((rc = cosmo_futex_fix_timeout (&tsmem, clock, abstime, &timeout))) goto Finished; LOCKTRACE ("futex(%t [%d], %s, %#x, %s) → ...", @@ -263,13 +274,13 @@ int nsync_futex_wait_ (atomic_int *w, int expect, char pshare, tib = __get_tls(); pt = (struct PosixThread *)tib->tib_pthread; - if (nsync_futex_.is_supported) { + if (g_cosmo_futex.is_supported) { e = errno; if (IsWindows ()) { // Windows 8 futexes don't support multiple processes :( if (pshare) goto Polyfill; sigset_t m = __sig_block (); - rc = nsync_futex_wait_win32_ (w, expect, pshare, clock, timeout, pt, m); + rc = cosmo_futex_wait_win32 (w, expect, pshare, clock, timeout, pt, m); __sig_unblock (m); } else if (IsXnu ()) { @@ -293,7 +304,7 @@ int nsync_futex_wait_ (atomic_int *w, int expect, char pshare, op = UL_COMPARE_AND_WAIT; } if (timeout) { - us = nsync_time_64to32u (timespec_tomicros (*timeout)); + us = cosmo_time_64to32u (timespec_tomicros (*timeout)); } else { us = -1u; } @@ -333,7 +344,7 @@ int nsync_futex_wait_ (atomic_int *w, int expect, char pshare, } } else { Polyfill: - rc = nsync_futex_polyfill_ (w, expect, clock, timeout); + rc = cosmo_futex_polyfill (w, expect, clock, timeout); } Finished: @@ -343,21 +354,28 @@ Finished: DescribeTimespec (0, abstime), DescribeErrno (rc)); + END_CANCELATION_POINT; return rc; } -int nsync_futex_wake_ (atomic_int *w, int count, char pshare) { +/** + * Wakes futex. + * + * @param w is your futex + * @param count is number of threads to wake (usually 1 or `INT_MAX`) + * @param pshare is `PTHREAD_PROCESS_PRIVATE` / `PTHREAD_PROCESS_SHARED` + * @return number of threads woken on success, or -errno on error + */ +int cosmo_futex_wake (atomic_int *w, int count, char pshare) { int rc, op, fop; - ASSERT (count == 1 || count == INT_MAX); - - cosmo_once (&nsync_futex_.once, nsync_futex_init_); + cosmo_once (&g_cosmo_futex.once, cosmo_futex_init); op = FUTEX_WAKE; if (pshare == PTHREAD_PROCESS_PRIVATE) - op |= nsync_futex_.FUTEX_PRIVATE_FLAG_; + op |= g_cosmo_futex.FUTEX_PRIVATE_FLAG_; - if (nsync_futex_.is_supported) { + if (g_cosmo_futex.is_supported) { if (IsWindows ()) { if (pshare) { goto Polyfill; @@ -379,7 +397,7 @@ int nsync_futex_wake_ (atomic_int *w, int count, char pshare) { op |= ULF_WAKE_ALL; } rc = ulock_wake (op, w, 0); - ASSERT (!rc || rc == -ENOENT); + unassert (!rc || rc == -ENOENT); if (!rc) { rc = 1; } else if (rc == -ENOENT) { diff --git a/libc/intrin/futex.S b/libc/intrin/cosmo_futex_thunk.S similarity index 96% rename from libc/intrin/futex.S rename to libc/intrin/cosmo_futex_thunk.S index 67c1d9822..ad65cc106 100644 --- a/libc/intrin/futex.S +++ b/libc/intrin/cosmo_futex_thunk.S @@ -20,17 +20,16 @@ #include "libc/macros.h" .privileged -_futex: +cosmo_futex_thunk: + beg + pro #ifdef __x86_64__ - push %rbp - mov %rsp,%rbp mov %rcx,%r10 mov __NR_futex,%eax clc syscall jnc 1f neg %eax -1: pop %rbp #elif defined(__aarch64__) ldr x7,=__hostos ldr w7,[x7] @@ -46,5 +45,7 @@ _futex: #else #error "unsupported architecture" #endif /* __x86_64__ */ -1: ret - .endfn _futex,globl,hidden +1: epi + ret + end + .endfn cosmo_futex_thunk,globl,hidden diff --git a/libc/intrin/typeinfo.S b/libc/intrin/count.c similarity index 74% rename from libc/intrin/typeinfo.S rename to libc/intrin/count.c index 0b35f0f72..d4f4365bb 100644 --- a/libc/intrin/typeinfo.S +++ b/libc/intrin/count.c @@ -1,7 +1,7 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│ vi: set noet ft=asm ts=8 sw=8 fenc=utf-8 :vi │ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ Copyright 2024 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,11 +16,11 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/macros.h" +#include "libc/atomic.h" +#include "libc/stdalign.h" +#include "libc/thread/thread.h" -// __cxxabiv1::__function_type_info (?) -// Because Clang in MODE=dbg doesn't respect -fno-rtti - .balign 8 -_ZTVN10__cxxabiv120__function_type_infoE: - .quad 0 - .endobj _ZTVN10__cxxabiv120__function_type_infoE,globl +// this counter is important because pthread_exit() needs to know if +// it's an orphan thread, without needing to acquire _pthread_lock() +// which causes contention and a file descriptor explosion on netbsd +alignas(64) atomic_uint _pthread_count = 1; diff --git a/libc/intrin/cursor.c b/libc/intrin/cursor.c index e0c686d4f..b89b1be27 100644 --- a/libc/intrin/cursor.c +++ b/libc/intrin/cursor.c @@ -20,16 +20,13 @@ #include "libc/intrin/atomic.h" #include "libc/intrin/fds.h" #include "libc/runtime/runtime.h" +#include "libc/thread/posixthread.internal.h" struct Cursor *__cursor_new(void) { struct Cursor *c; if ((c = _mapanon(sizeof(struct Cursor)))) { if ((c->shared = _mapshared(sizeof(struct CursorShared)))) { - pthread_mutexattr_t attr; - pthread_mutexattr_init(&attr); - pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); - pthread_mutex_init(&c->shared->lock, &attr); - pthread_mutexattr_destroy(&attr); + c->shared->lock = (pthread_mutex_t)PTHREAD_SHARED_MUTEX_INITIALIZER_NP; } else { munmap(c, sizeof(struct Cursor)); c = 0; @@ -56,9 +53,9 @@ int __cursor_unref(struct Cursor *c) { } void __cursor_lock(struct Cursor *c) { - pthread_mutex_lock(&c->shared->lock); + _pthread_mutex_lock(&c->shared->lock); } void __cursor_unlock(struct Cursor *c) { - pthread_mutex_unlock(&c->shared->lock); + _pthread_mutex_unlock(&c->shared->lock); } diff --git a/libc/intrin/cxaatexit.h b/libc/intrin/cxaatexit.h index 45b566b70..ac89d7614 100644 --- a/libc/intrin/cxaatexit.h +++ b/libc/intrin/cxaatexit.h @@ -18,9 +18,9 @@ struct CxaAtexitBlocks { extern struct CxaAtexitBlocks __cxa_blocks; -void __cxa_lock(void) libcesque; -void __cxa_unlock(void) libcesque; -void __cxa_thread_finalize(void) libcesque; +void __cxa_lock(void) dontthrow; +void __cxa_unlock(void) dontthrow; +void __cxa_thread_finalize(void) dontthrow; void __cxa_printexits(FILE *, void *) libcesque; int __cxa_thread_atexit_impl(void *, void *, void *); diff --git a/libc/intrin/cxalock.c b/libc/intrin/cxalock.c index e0d43f534..cb5256757 100644 --- a/libc/intrin/cxalock.c +++ b/libc/intrin/cxalock.c @@ -17,22 +17,15 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/intrin/cxaatexit.h" +#include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" -static pthread_mutex_t __cxa_lock_obj; - -void __cxa_wipe(void) { - pthread_mutex_init(&__cxa_lock_obj, 0); -} +pthread_mutex_t __cxa_lock_obj = PTHREAD_MUTEX_INITIALIZER; void __cxa_lock(void) { - pthread_mutex_lock(&__cxa_lock_obj); + _pthread_mutex_lock(&__cxa_lock_obj); } void __cxa_unlock(void) { - pthread_mutex_unlock(&__cxa_lock_obj); -} - -__attribute__((__constructor__(60))) static textstartup void __cxa_init() { - pthread_atfork(__cxa_lock, __cxa_unlock, __cxa_wipe); + _pthread_mutex_unlock(&__cxa_lock_obj); } diff --git a/libc/intrin/deadlock.c b/libc/intrin/deadlock.c new file mode 100644 index 000000000..57da577a4 --- /dev/null +++ b/libc/intrin/deadlock.c @@ -0,0 +1,277 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 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 "ape/sections.internal.h" +#include "libc/assert.h" +#include "libc/atomic.h" +#include "libc/cosmo.h" +#include "libc/dce.h" +#include "libc/errno.h" +#include "libc/intrin/atomic.h" +#include "libc/intrin/kprintf.h" +#include "libc/intrin/maps.h" +#include "libc/macros.h" +#include "libc/str/str.h" +#include "libc/thread/lock.h" +#include "libc/thread/thread.h" +#include "libc/thread/tls.h" + +/** + * @fileoverview deadlock detector for statically allocated locks + * + * This module helps you spot multi-threading bugs in your program. + * High-level abstractions like mutexes are much easier to use than + * atomics, but they still carry their own non-obvious dangers. For + * example, nesting locks need to be nested in a consistent way and + * normal mutexes can't be required recursively. Normally this will + * cause your program to deadlock, i.e. hang indefinitely, but this + * module can detect such conditions and return errors instead, and + * better yet print helpful information when using `cosmocc -mdbg`. + */ + +#define ABI privileged optimizesize + +// building our visitor function using this optimizesize keyword shrinks +// the stack memory requirement from 7168 to 2048 bytes. totally amazing +// although please note this maximum isn't a hard limit. for normal mode +// builds your posix mandated mutex error checking will be less accurate +// but still helpful and reliable, although your cosmocc -mdbg will trap +// and report that you've run into the limit, so you can talk to justine +#define MAX_LOCKS 64 + +// cosmo's tib reserves space for 64 nested locks before things degrade. +// the cosmopolitan c runtime defines 16 locks, which are all registered +// with pthread_atfork(). it means you get to have 48 mutexes right now, +// and if you register all of them, then calling fork() will cause there +// to be 2080 edges in your lock graph. talk to justine if you need more +// because we're obviously going to need to find a way to make this grow +#define LOCK_EDGES_MAX 2080 + +// supported lock objects must define `void *_edges` +#define LOCK_EDGES_OFFSET 0 +static_assert(offsetof(struct MapLock, edges) == LOCK_EDGES_OFFSET); +static_assert(offsetof(pthread_mutex_t, _edges) == LOCK_EDGES_OFFSET); + +struct LockEdge { + struct LockEdge *next; + void *dest; +}; + +struct VisitedLock { + struct VisitedLock *next; + void *lock; +}; + +typedef _Atomic(struct LockEdge *) LockEdges; + +static struct DeadlockDetector { + atomic_size_t edges_allocated; + struct LockEdge edges_memory[LOCK_EDGES_MAX]; +} __deadlock; + +forceinline struct CosmoTib *__deadlock_tls(void) { + return __get_tls_privileged(); +} + +forceinline LockEdges *get_lock_edges(void *lock) { + return (LockEdges *)((char *)lock + LOCK_EDGES_OFFSET); +} + +forceinline struct LockEdge *load_lock_edges(LockEdges *edges) { + return atomic_load_explicit(edges, memory_order_relaxed); +} + +ABI static int is_static_memory(void *lock) { + return _etext <= (unsigned char *)lock && (unsigned char *)lock < _end; +} + +ABI static struct LockEdge *__deadlock_alloc(void) { + size_t edges_allocated = + atomic_load_explicit(&__deadlock.edges_allocated, memory_order_relaxed); + for (;;) { + if (edges_allocated == LOCK_EDGES_MAX) { + if (IsModeDbg()) { + kprintf("error: cosmo LOCK_EDGES_MAX needs to be increased\n"); + DebugBreak(); + } + return 0; + } + if (atomic_compare_exchange_weak_explicit( + &__deadlock.edges_allocated, &edges_allocated, edges_allocated + 1, + memory_order_relaxed, memory_order_relaxed)) + return &__deadlock.edges_memory[edges_allocated]; + } +} + +ABI static void __deadlock_add_edge(void *from, void *dest) { + LockEdges *edges = get_lock_edges(from); + for (struct LockEdge *e = load_lock_edges(edges); e; e = e->next) + if (e->dest == dest) + return; + struct LockEdge *edge; + if ((edge = __deadlock_alloc())) { + edge->next = load_lock_edges(edges); + edge->dest = dest; + // we tolerate duplicate elements in the interest of performance. + // once an element is inserted, it's never removed. that's why we + // don't need need to worry about the aba problem. the cas itself + // is very important since it ensures inserted edges aren't lost. + for (;;) + if (atomic_compare_exchange_weak_explicit(edges, &edge->next, edge, + memory_order_relaxed, + memory_order_relaxed)) + break; + } +} + +ABI static bool __deadlock_visit(void *lock, struct VisitedLock *visited, + int notrap, int depth) { + if (++depth == MAX_LOCKS) { + if (IsModeDbg()) { + kprintf("error: too much recursion in deadlock detector\n"); + DebugBreak(); + } + return false; + } + for (struct VisitedLock *v = visited; v; v = v->next) { + if (v->lock == lock) { + if (IsModeDbg() && !notrap) { + // lock hierarchy violated! + // + // when you lock mutexes in a nested way, your locks must be + // nested in the same order globally. otherwise deadlocks might + // occur. for example, if you say in your first thread + // + // pthread_mutex_lock(&x); + // pthread_mutex_lock(&y); + // pthread_mutex_unlock(&y); + // pthread_mutex_unlock(&x); + // + // then in your second thread you say + // + // pthread_mutex_lock(&y); + // pthread_mutex_lock(&x); + // pthread_mutex_unlock(&x); + // pthread_mutex_unlock(&y); + // + // then a deadlock might happen, because {x→y, y→x} is cyclic! + // they don't happen often, but this is the kind of thing that + // matters if you want to build carrier grade production stuff + kprintf("error: cycle detected in directed graph of nested locks\n"); + for (struct VisitedLock *v = visited; v; v = v->next) + kprintf("\t- %t\n", v->lock); // strongly connected component + DebugBreak(); + } + return true; + } + } + LockEdges *edges = get_lock_edges(lock); + struct VisitedLock visit = {visited, lock}; + for (struct LockEdge *e = load_lock_edges(edges); e; e = e->next) + if (__deadlock_visit(e->dest, &visit, notrap, depth)) + return true; + return false; +} + +/** + * Returns true if lock is already locked by calling thread. + * + * This function may return false negatives if we run out of TLS memory. + * That suboptimal condition will be reported in debug mode. + * + * @return 1 if lock is certainly owned by calling thread, 0 if lock is + * certainly not owned by calling thread, and -1 if we're uncertain + */ +ABI int __deadlock_tracked(void *lock) { + int full = 1; + int owned = 0; + struct CosmoTib *tib = __deadlock_tls(); + for (int i = 0; i < ARRAYLEN(tib->tib_locks); ++i) { + full &= tib->tib_locks[i] != NULL; + owned |= tib->tib_locks[i] == lock; + } + if (full) + return -1; + if (!owned && !is_static_memory(lock)) + return -1; + return owned; +} + +/** + * Records that lock is held by thread. + * @param notrap can prevent error printing and debug breaking + * @asyncsignalsafe + */ +ABI void __deadlock_track(void *lock, int notrap) { + if (!notrap && !is_static_memory(lock)) + return; + struct CosmoTib *tib = __deadlock_tls(); + for (int i = 0; i < ARRAYLEN(tib->tib_locks); ++i) { + if (!tib->tib_locks[i]) { + tib->tib_locks[i] = lock; + return; + } + } + if (IsModeDbg()) { + kprintf("error: cosmo tls max lock depth needs to be increased!\n"); + DebugBreak(); + } +} + +/** + * Records relationship for all held locks to `lock`. + * @param notrap can prevent error printing and debug breaking + * @asyncsignalsafe + */ +ABI void __deadlock_record(void *lock, int notrap) { + if (!notrap && !is_static_memory(lock)) + return; + struct CosmoTib *tib = __deadlock_tls(); + for (int i = 0; i < ARRAYLEN(tib->tib_locks); ++i) + if (tib->tib_locks[i] && tib->tib_locks[i] != lock) + __deadlock_add_edge(tib->tib_locks[i], lock); +} + +/** + * Returns EDEADLK if locking `lock` could cause a deadlock. + * @param notrap can prevent error printing and debug breaking + * @asyncsignalsafe + */ +ABI int __deadlock_check(void *lock, int notrap) { + struct CosmoTib *tib = __deadlock_tls(); + for (int i = 0; i < ARRAYLEN(tib->tib_locks); ++i) { + if (tib->tib_locks[i] == lock) + return 0; + if (tib->tib_locks[i]) { + struct VisitedLock visit = {0, tib->tib_locks[i]}; + if (__deadlock_visit(lock, &visit, notrap, 0)) + return EDEADLK; + } + } + return 0; +} + +/** + * Records that lock isn't held by thread. + * @asyncsignalsafe + */ +ABI void __deadlock_untrack(void *lock) { + struct CosmoTib *tib = __deadlock_tls(); + for (int i = 0; i < ARRAYLEN(tib->tib_locks); ++i) + tib->tib_locks[i] = tib->tib_locks[i] != lock ? tib->tib_locks[i] : 0; +} diff --git a/libc/intrin/demangle.c b/libc/intrin/demangle.c index ad21c1eb1..c44803f12 100644 --- a/libc/intrin/demangle.c +++ b/libc/intrin/demangle.c @@ -91,6 +91,8 @@ Copyright (c) 2024 Justine Tunney "); * */ +#define ABI privileged optimizesize + #define DEMANGLE_NO_FLOATING_POINT #define ASSERT(x) (void)0 @@ -103,6 +105,7 @@ Copyright (c) 2024 Justine Tunney "); #define ELFTC_SUCCESS 1 #define VECTOR_DEF_CAPACITY 1 +#define MAX_DEPTH 20 typedef unsigned short index_t; @@ -188,6 +191,7 @@ struct demangle_data { enum type_qualifier ref_qualifier_type; /* ref qualifier type */ enum push_qualifier push_qualifier; /* which qualifiers to push */ int func_type; + int depth; const char *cur; /* current mangled name ptr */ const char *last_sname; /* last source name */ intptr_t jmpbuf[5]; @@ -220,16 +224,18 @@ static int demangle_read_sname(struct demangle_data *); static int demangle_read_subst(struct demangle_data *); static int demangle_read_type(struct demangle_data *, struct type_delimit *); -static privileged size_t +ABI static size_t demangle_strlen(const char *s) { size_t n = 0; - while (*s++) + while (*s++) { + asm volatile("" ::: "memory"); ++n; + } return n; } -static privileged char * +ABI static char * demangle_stpcpy(char *d, const char *s) { size_t i = 0; @@ -240,7 +246,7 @@ demangle_stpcpy(char *d, const char *s) } } -static privileged void * +ABI static void * demangle_mempcpy(void *a, const void *b, size_t n) { char *d = a; @@ -250,14 +256,14 @@ demangle_mempcpy(void *a, const void *b, size_t n) return d; } -static privileged void * +ABI static void * demangle_memcpy(void *a, const void *b, size_t n) { demangle_mempcpy(a, b, n); return a; } -static privileged int +ABI static int demangle_strncmp(const char *a, const char *b, size_t n) { size_t i = 0; @@ -268,7 +274,7 @@ demangle_strncmp(const char *a, const char *b, size_t n) return (a[i] & 0xff) - (b[i] & 0xff); } -static privileged int +ABI static int demangle_memcmp(const void *a, const void *b, size_t n) { int c; @@ -283,7 +289,7 @@ demangle_memcmp(const void *a, const void *b, size_t n) return 0; } -static privileged void +ABI static void demangle_strlcpy(char *dst, const char *src, size_t dsize) { size_t remain; @@ -295,7 +301,7 @@ demangle_strlcpy(char *dst, const char *src, size_t dsize) *dst = 0; } -static privileged long +ABI static long demangle_strtol(const char *s, int base) { static const uint8_t demangle_base36[80] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, @@ -312,7 +318,7 @@ demangle_strtol(const char *s, int base) return x; } -static privileged char * +ABI static char * demangle_strstr(const char *haystack, const char *needle) { size_t i; @@ -333,7 +339,7 @@ demangle_strstr(const char *haystack, const char *needle) return 0; } -static privileged char * +ABI static char * demangle_utoa(char *p, unsigned long long x) { char t; @@ -354,7 +360,7 @@ demangle_utoa(char *p, unsigned long long x) return p + i; } -static privileged char * +ABI static char * demangle_itoa(char *p, long long x) { if (x < 0) @@ -362,7 +368,7 @@ demangle_itoa(char *p, long long x) return demangle_utoa(p, x); } -static privileged void +ABI static void demangle_free(struct demangle_data *h, void *ptr) { index_t base; @@ -379,14 +385,17 @@ demangle_free(struct demangle_data *h, void *ptr) } } -static privileged returnspointerwithnoaliases returnsnonnull void * -demangle_malloc(struct demangle_data *h, int a, int n) +ABI static returnspointerwithnoaliases returnsnonnull void * +demangle_malloc(struct demangle_data *h, long a, long n) { - int rem; + long rem; uintptr_t ptr; index_t next, next2; index_t *link, *link2; - int b = sizeof(index_t); + long b = sizeof(index_t); + + if (n < 0 || n >= 32768) + __builtin_longjmp(h->jmpbuf, 1); /* Roundup size. */ n += a - 1; @@ -433,7 +442,7 @@ demangle_malloc(struct demangle_data *h, int a, int n) } } -static privileged returnspointerwithnoaliases char * +ABI static returnspointerwithnoaliases char * demangle_strdup(struct demangle_data *h, const char *s) { char *d = 0; @@ -445,7 +454,7 @@ demangle_strdup(struct demangle_data *h, const char *s) return d; } -static privileged void +ABI static void demangle_vector_str_dest(struct demangle_data *h, struct vector_str *v) { int i; @@ -454,7 +463,7 @@ demangle_vector_str_dest(struct demangle_data *h, struct vector_str *v) demangle_free(h, v->container); } -static privileged void +ABI static void demangle_vector_type_qualifier_dest(struct demangle_data *d, struct vector_type_qualifier *v) { @@ -462,7 +471,7 @@ demangle_vector_type_qualifier_dest(struct demangle_data *d, demangle_vector_str_dest(d, &v->ext_name); } -static privileged void +ABI static void demangle_stack_str_init(struct stack_str *ss) { ss->str = ss->buf; @@ -471,7 +480,7 @@ demangle_stack_str_init(struct stack_str *ss) ss->cap = sizeof(ss->buf); } -static privileged void +ABI static void demangle_stack_str_append(struct demangle_data *h, struct stack_str *ss, const char *str, size_t len) { @@ -494,7 +503,7 @@ demangle_stack_str_append(struct demangle_data *h, struct stack_str *ss, #define demangle_stack_str_append_str(h, ss, s) \ demangle_stack_str_append(h, ss, s, demangle_strlen(s)) -static privileged size_t +ABI static size_t demangle_get_strlen_sum(struct demangle_data *h, const struct vector_str *v) { size_t i, len = 0; @@ -504,7 +513,7 @@ demangle_get_strlen_sum(struct demangle_data *h, const struct vector_str *v) return len; } -static privileged int +ABI static int demangle_demangle_strncmp(const char *a, const char *b, size_t n) { size_t i = 0; @@ -522,7 +531,7 @@ demangle_demangle_strncmp(const char *a, const char *b, size_t n) * @param l Length of the string. * @return -1 at failed, 0 at not found, 1 at found. */ -static privileged int +ABI static int demangle_vector_str_find(struct demangle_data *h, const struct vector_str *v, const char *o, size_t l) { @@ -546,7 +555,7 @@ demangle_vector_str_find(struct demangle_data *h, const struct vector_str *v, * @param l Length of the string. * @return NULL at failed or NUL terminated new allocated string. */ -static privileged char * +ABI static char * demangle_vector_str_get_flat(struct demangle_data *ddata, const struct vector_str *v, size_t *l) { @@ -572,7 +581,7 @@ demangle_vector_str_get_flat(struct demangle_data *ddata, return rtn; } -static privileged void +ABI static void demangle_vector_str_grow(struct demangle_data *ddata, struct vector_str *v) { size_t i, tmp_cap; @@ -600,7 +609,7 @@ demangle_vector_str_grow(struct demangle_data *ddata, struct vector_str *v) * @brief Initialize vector_str. * @return false at failed, true at success. */ -static privileged void +ABI static void demangle_vector_str_init(struct demangle_data *ddata, struct vector_str *v) { v->size = 0; @@ -616,7 +625,7 @@ demangle_vector_str_init(struct demangle_data *ddata, struct vector_str *v) * @brief Remove last element in vector_str. * @return false at failed, true at success. */ -static privileged bool +ABI static bool demangle_vector_str_pop(struct vector_str *v) { if (!v) @@ -636,7 +645,7 @@ demangle_vector_str_pop(struct vector_str *v) * @brief Push back string to vector. * @return false at failed, true at success. */ -static privileged bool +ABI static bool demangle_vector_str_push(struct demangle_data *ddata, struct vector_str *v, const char *str, size_t len) { @@ -660,7 +669,7 @@ demangle_vector_str_push(struct demangle_data *ddata, struct vector_str *v, * @brief Push front org vector to det vector. * @return false at failed, true at success. */ -static privileged bool +ABI static bool demangle_vector_str_push_vector_head(struct demangle_data *ddata, struct vector_str *dst, struct vector_str *org) { @@ -693,7 +702,7 @@ demangle_vector_str_push_vector_head(struct demangle_data *ddata, * @brief Push org vector to the tail of det vector. * @return false at failed, true at success. */ -static privileged bool +ABI static bool demangle_vector_str_push_vector(struct demangle_data *ddata, struct vector_str *dst, struct vector_str *org) { @@ -731,7 +740,7 @@ demangle_vector_str_push_vector(struct demangle_data *ddata, * If r_len is not NULL, string length will be returned. * @return NULL at failed or NUL terminated new allocated string. */ -static privileged returnspointerwithnoaliases char * +ABI static returnspointerwithnoaliases char * demangle_vector_str_substr(struct demangle_data *ddata, const struct vector_str *v, size_t begin, size_t end, size_t *r_len) { @@ -757,7 +766,7 @@ demangle_vector_str_substr(struct demangle_data *ddata, return rtn; } -static privileged int +ABI static int demangle_vector_read_cmd_pop(struct vector_read_cmd *v) { if (!v->size) @@ -770,7 +779,7 @@ demangle_vector_read_cmd_pop(struct vector_read_cmd *v) return 1; } -static privileged void +ABI static void demangle_vector_read_cmd_init(struct demangle_data *ddata, struct vector_read_cmd *v) { @@ -781,7 +790,7 @@ demangle_vector_read_cmd_init(struct demangle_data *ddata, alignof(*v->r_container), sizeof(*v->r_container) * v->capacity); } -static privileged void +ABI static void demangle_data_init(struct demangle_data *d, const char *cur) { demangle_vector_str_init(d, &d->output); @@ -811,7 +820,7 @@ demangle_data_init(struct demangle_data *d, const char *cur) d->last_sname = NULL; } -static privileged int +ABI static int demangle_push_str(struct demangle_data *ddata, const char *str, size_t len) { if (!str || !len) @@ -828,7 +837,7 @@ demangle_push_str(struct demangle_data *ddata, const char *str, size_t len) } #ifndef DEMANGLE_NO_FLOATING_POINT -static privileged int +ABI static int demangle_push_fp(struct demangle_data *ddata, char *decoder(struct demangle_data *, const char *, size_t)) { @@ -857,13 +866,13 @@ demangle_push_fp(struct demangle_data *ddata, } #endif // DEMANGLE_NO_FLOATING_POINT -static privileged int +ABI static int demangle_pop_str(struct demangle_data *ddata) { return demangle_vector_str_pop(ddata->cur_output); } -static privileged int +ABI static int demangle_push_subst(struct demangle_data *ddata, const char *str, size_t len) { if (!str || !len) @@ -875,7 +884,7 @@ demangle_push_subst(struct demangle_data *ddata, const char *str, size_t len) return 1; } -static privileged int +ABI static int demangle_push_subst_v(struct demangle_data *ddata, struct vector_str *v) { int rtn; @@ -895,7 +904,7 @@ demangle_push_subst_v(struct demangle_data *ddata, struct vector_str *v) return rtn; } -static privileged int +ABI static int demangle_push_type_qualifier(struct demangle_data *ddata, struct vector_type_qualifier *v, const char *type_str) { @@ -1128,7 +1137,7 @@ demangle_push_type_qualifier(struct demangle_data *ddata, return 1; } -static privileged int +ABI static int demangle_get_subst(struct demangle_data *ddata, size_t idx) { size_t len; @@ -1146,7 +1155,7 @@ demangle_get_subst(struct demangle_data *ddata, size_t idx) return 1; } -static privileged int +ABI static int demangle_get_tmpl_param(struct demangle_data *ddata, size_t idx) { size_t len; @@ -1163,7 +1172,7 @@ demangle_get_tmpl_param(struct demangle_data *ddata, size_t idx) return 1; } -static privileged int +ABI static int demangle_read_array(struct demangle_data *ddata) { size_t i, num_len, exp_len, p_idx, idx; @@ -1235,7 +1244,7 @@ demangle_read_array(struct demangle_data *ddata) #ifndef DEMANGLE_NO_FLOATING_POINT /* Simple hex to integer function used by decode_to_* function. */ -static privileged int +ABI static int hex_to_dec(char c) { switch (c) { @@ -1283,7 +1292,7 @@ hex_to_dec(char c) * Todo * Replace these functions to macro. */ -static privileged returnspointerwithnoaliases char * +ABI static returnspointerwithnoaliases char * decode_fp_to_double(struct demangle_data *ddata, const char *p, size_t len) { double f; @@ -1327,7 +1336,7 @@ again: return rtn; } -static privileged returnspointerwithnoaliases char * +ABI static returnspointerwithnoaliases char * decode_fp_to_float(struct demangle_data *ddata, const char *p, size_t len) { size_t i, rtn_len, limit; @@ -1369,7 +1378,7 @@ again: return rtn; } -static privileged returnspointerwithnoaliases char * +ABI static returnspointerwithnoaliases char * decode_fp_to_long_double(struct demangle_data *ddata, const char *p, size_t len) { long double f; @@ -1413,7 +1422,7 @@ again: return rtn; } -static privileged returnspointerwithnoaliases char * +ABI static returnspointerwithnoaliases char * decode_fp_to_float128(struct demangle_data *ddata, const char *p, size_t len) { long double f; @@ -1470,7 +1479,7 @@ decode_fp_to_float128(struct demangle_data *ddata, const char *p, size_t len) } } -static privileged returnspointerwithnoaliases char * +ABI static returnspointerwithnoaliases char * decode_fp_to_float80(struct demangle_data *ddata, const char *p, size_t len) { long double f; @@ -1533,7 +1542,7 @@ decode_fp_to_float80(struct demangle_data *ddata, const char *p, size_t len) #endif // DEMANGLE_NO_FLOATING_POINT -static privileged int +ABI static int demangle_read_expr_primary(struct demangle_data *ddata) { const char *num; @@ -1625,7 +1634,7 @@ demangle_read_expr_primary(struct demangle_data *ddata) * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=31775 * http://gcc.gnu.org/viewcvs?view=rev&revision=124467 */ -static privileged int +ABI static int demangle_local_source_name(struct demangle_data *ddata) { /* L */ @@ -1651,7 +1660,7 @@ demangle_local_source_name(struct demangle_data *ddata) * read unqualified-name, unqualified name are operator-name, ctor-dtor-name, * source-name */ -static privileged int +ABI static int demangle_read_uqname(struct demangle_data *ddata) { size_t len; @@ -2080,7 +2089,7 @@ demangle_read_uqname(struct demangle_data *ddata) * Read template parameter that forms in 'T[number]_'. * This function much like to read_subst but only for types. */ -static privileged int +ABI static int demangle_read_tmpl_param(struct demangle_data *ddata) { long nth; @@ -2098,10 +2107,11 @@ demangle_read_tmpl_param(struct demangle_data *ddata) /* T_ is first */ ++nth; - while (*ddata->cur != '_') + while (*ddata->cur && *ddata->cur != '_') ++ddata->cur; - ASSERT(nth > 0); + if (nth <= 0) + return 0; return demangle_get_tmpl_param(ddata, nth); } @@ -2110,7 +2120,7 @@ demangle_read_tmpl_param(struct demangle_data *ddata) return 0; } -static privileged int +ABI static int demangle_vector_read_cmd_push(struct demangle_data *ddata, struct vector_read_cmd *v, enum read_cmd cmd, void *data) { @@ -2139,7 +2149,7 @@ demangle_vector_read_cmd_push(struct demangle_data *ddata, return 1; } -static privileged int +ABI static int demangle_read_tmpl_arg(struct demangle_data *ddata) { if (*ddata->cur == '\0') @@ -2158,7 +2168,7 @@ demangle_read_tmpl_arg(struct demangle_data *ddata) return demangle_read_type(ddata, NULL); } -static privileged int +ABI static int demangle_read_tmpl_args(struct demangle_data *ddata) { struct vector_str *v; @@ -2211,7 +2221,7 @@ demangle_read_tmpl_args(struct demangle_data *ddata) return demangle_vector_read_cmd_pop(&ddata->cmd); } -static privileged int +ABI static int demangle_read_expression_trinary(struct demangle_data *ddata, const char *name1, size_t len1, const char *name2, size_t len2) { @@ -2230,7 +2240,7 @@ demangle_read_expression_trinary(struct demangle_data *ddata, const char *name1, return demangle_read_expression(ddata); } -static privileged int +ABI static int demangle_read_expression_unary(struct demangle_data *ddata, const char *name, size_t len) { @@ -2242,7 +2252,7 @@ demangle_read_expression_unary(struct demangle_data *ddata, const char *name, return demangle_push_str(ddata, name, len); } -static privileged int +ABI static int demangle_read_expression_binary(struct demangle_data *ddata, const char *name, size_t len) { @@ -2256,8 +2266,8 @@ demangle_read_expression_binary(struct demangle_data *ddata, const char *name, return demangle_read_expression(ddata); } -static privileged int -demangle_read_expression(struct demangle_data *ddata) +ABI static int +demangle_read_expression_impl(struct demangle_data *ddata) { if (*ddata->cur == '\0') return 0; @@ -2538,7 +2548,18 @@ demangle_read_expression(struct demangle_data *ddata) return 0; } -static privileged int +ABI static int +demangle_read_expression(struct demangle_data *ddata) +{ + if (ddata->depth == MAX_DEPTH) + __builtin_longjmp(ddata->jmpbuf, 1); + ++ddata->depth; + int res = demangle_read_expression_impl(ddata); + --ddata->depth; + return res; +} + +ABI static int demangle_read_expression_flat(struct demangle_data *ddata, char **str) { struct vector_str *output; @@ -2567,7 +2588,7 @@ demangle_read_expression_flat(struct demangle_data *ddata, char **str) } /* size, capacity, ext_name */ -static privileged void +ABI static void demangle_vector_type_qualifier_init(struct demangle_data *ddata, struct vector_type_qualifier *v) { @@ -2583,7 +2604,7 @@ demangle_vector_type_qualifier_init(struct demangle_data *ddata, demangle_vector_str_init(ddata, &v->ext_name); } -static privileged struct read_cmd_item * +ABI static struct read_cmd_item * demangle_vector_read_cmd_find(struct vector_read_cmd *v, enum read_cmd dst) { int i; @@ -2598,7 +2619,7 @@ demangle_vector_read_cmd_find(struct vector_read_cmd *v, enum read_cmd dst) return 0; } -static privileged int +ABI static int demangle_read_function(struct demangle_data *ddata, int *ext_c, struct vector_type_qualifier *v) { @@ -2734,7 +2755,7 @@ demangle_read_function(struct demangle_data *ddata, int *ext_c, return 1; } -static privileged int +ABI static int demangle_read_offset_number(struct demangle_data *ddata) { bool negative; @@ -2752,7 +2773,7 @@ demangle_read_offset_number(struct demangle_data *ddata) start = ddata->cur; } - while (*ddata->cur != '_') + while (*ddata->cur && *ddata->cur != '_') ++ddata->cur; if (negative && !DEM_PUSH_STR(ddata, "-")) @@ -2770,7 +2791,7 @@ demangle_read_offset_number(struct demangle_data *ddata) return 1; } -static privileged int +ABI static int demangle_read_nv_offset(struct demangle_data *ddata) { if (!DEM_PUSH_STR(ddata, "offset : ")) @@ -2779,7 +2800,7 @@ demangle_read_nv_offset(struct demangle_data *ddata) return demangle_read_offset_number(ddata); } -static privileged int +ABI static int demangle_read_v_offset(struct demangle_data *ddata) { if (!DEM_PUSH_STR(ddata, "offset : ")) @@ -2795,7 +2816,7 @@ demangle_read_v_offset(struct demangle_data *ddata) } /* read offset, offset are nv-offset, v-offset */ -static privileged int +ABI static int demangle_read_offset(struct demangle_data *ddata) { if (*ddata->cur == 'h') { @@ -2809,7 +2830,7 @@ demangle_read_offset(struct demangle_data *ddata) return 0; } -static privileged int +ABI static int demangle_read_type_flat(struct demangle_data *ddata, char **str) { struct vector_str *output; @@ -2841,7 +2862,7 @@ demangle_read_type_flat(struct demangle_data *ddata, char **str) * read number * number ::= [n] */ -static privileged int +ABI static int demangle_read_number(struct demangle_data *ddata, long *rtn) { long len, negative_factor; @@ -2859,19 +2880,18 @@ demangle_read_number(struct demangle_data *ddata, long *rtn) return 0; len = demangle_strtol(ddata->cur, 10); + if (len < 0) + __builtin_longjmp(ddata->jmpbuf, 1); while (ELFTC_ISDIGIT(*ddata->cur)) ++ddata->cur; - ASSERT(len >= 0); - ASSERT(negative_factor == 1 || negative_factor == -1); - *rtn = len * negative_factor; return 1; } -static privileged int +ABI static int demangle_read_number_as_string(struct demangle_data *ddata, char **str) { long n; @@ -2888,9 +2908,8 @@ demangle_read_number_as_string(struct demangle_data *ddata, char **str) return 1; } -/* read encoding, encoding are function name, data name, special-name */ -static privileged int -demangle_read_encoding(struct demangle_data *ddata) +ABI static int +demangle_read_encoding_impl(struct demangle_data *ddata) { char *name, *type, *num_str; long offset; @@ -3097,7 +3116,19 @@ demangle_read_encoding(struct demangle_data *ddata) return demangle_read_name(ddata); } -static privileged int +/* read encoding, encoding are function name, data name, special-name */ +ABI static int +demangle_read_encoding(struct demangle_data *ddata) +{ + if (ddata->depth == MAX_DEPTH) + __builtin_longjmp(ddata->jmpbuf, 1); + ++ddata->depth; + int res = demangle_read_encoding_impl(ddata); + --ddata->depth; + return res; +} + +ABI static int demangle_read_local_name(struct demangle_data *ddata) { struct vector_str local_name; @@ -3178,7 +3209,7 @@ demangle_read_local_name(struct demangle_data *ddata) return 1; } -static privileged int +ABI static int demangle_read_nested_name(struct demangle_data *ddata) { struct stack_str v; @@ -3266,8 +3297,8 @@ next: return 1; } -static privileged int -demangle_read_name(struct demangle_data *ddata) +ABI static int +demangle_read_name_impl(struct demangle_data *ddata) { struct stack_str v; struct vector_str *output; @@ -3328,7 +3359,18 @@ clean: return rtn; } -static privileged int +ABI static int +demangle_read_name(struct demangle_data *ddata) +{ + if (ddata->depth == MAX_DEPTH) + __builtin_longjmp(ddata->jmpbuf, 1); + ++ddata->depth; + int res = demangle_read_name_impl(ddata); + --ddata->depth; + return res; +} + +ABI static int demangle_read_name_flat(struct demangle_data *ddata, char **str) { struct vector_str *output; @@ -3356,7 +3398,7 @@ demangle_read_name_flat(struct demangle_data *ddata, char **str) return 1; } -static privileged int +ABI static int demangle_read_pointer_to_member(struct demangle_data *ddata, struct vector_type_qualifier *v) { @@ -3416,9 +3458,10 @@ clean1: } /* read source-name, source-name is */ -static privileged int +ABI static int demangle_read_sname(struct demangle_data *ddata) { + size_t lim; long len; int err; @@ -3438,12 +3481,15 @@ demangle_read_sname(struct demangle_data *ddata) ddata->last_sname = VEC_STR(ddata, ddata->cur_output, ddata->cur_output->size - 1); + lim = demangle_strlen(ddata->cur); + if (len > lim) + len = lim; ddata->cur += len; return 1; } -static privileged int +ABI static int demangle_read_subst_stdtmpl(struct demangle_data *ddata, const char *str) { struct vector_str *output; @@ -3481,7 +3527,7 @@ demangle_read_subst_stdtmpl(struct demangle_data *ddata, const char *str) return 1; } -static privileged int +ABI static int demangle_read_subst_std(struct demangle_data *ddata) { struct vector_str *output, v; @@ -3532,7 +3578,7 @@ demangle_read_subst_std(struct demangle_data *ddata) return 1; } -static privileged int +ABI static int demangle_read_subst(struct demangle_data *ddata) { long nth; @@ -3647,10 +3693,11 @@ demangle_read_subst(struct demangle_data *ddata) /* first was '_', so increase one */ ++nth; - while (*ddata->cur != '_') + while (*ddata->cur && *ddata->cur != '_') ++ddata->cur; - ASSERT(nth > 0); + if (nth <= 0) + return 0; return demangle_get_subst(ddata, nth); } @@ -3659,7 +3706,7 @@ demangle_read_subst(struct demangle_data *ddata) return 0; } -static privileged int +ABI static int demangle_vector_type_qualifier_push(struct demangle_data *ddata, struct vector_type_qualifier *v, enum type_qualifier t) { @@ -3688,8 +3735,8 @@ demangle_vector_type_qualifier_push(struct demangle_data *ddata, return 1; } -static privileged int -demangle_read_type(struct demangle_data *ddata, struct type_delimit *td) +ABI static int +demangle_read_type_impl(struct demangle_data *ddata, struct type_delimit *td) { struct vector_type_qualifier v; struct vector_str *output, sv; @@ -3881,7 +3928,7 @@ again: case 'E': /* unexpected end (except some things) */ - if (ddata->is_guard_variable) + if (td && ddata->is_guard_variable) td->paren = false; if (ddata->is_guard_variable || (ddata->ref_qualifier && ddata->is_functype)) { @@ -4102,6 +4149,8 @@ again: if (!demangle_vector_str_push(ddata, &v.ext_name, ddata->cur, len)) return 0; + if (len > demangle_strlen(ddata->cur)) + len = demangle_strlen(ddata->cur); ddata->cur += len; if (!demangle_vector_type_qualifier_push(ddata, &v, TYPE_EXT)) return 0; @@ -4209,7 +4258,18 @@ clean: return 0; } -static privileged int +ABI static int +demangle_read_type(struct demangle_data *ddata, struct type_delimit *td) +{ + if (ddata->depth == MAX_DEPTH) + __builtin_longjmp(ddata->jmpbuf, 1); + ++ddata->depth; + int res = demangle_read_type_impl(ddata, td); + --ddata->depth; + return res; +} + +ABI static int demangle_copy_output(struct demangle_data *ddata, char *buf, const struct vector_str *v, size_t buflen) { @@ -4232,14 +4292,14 @@ demangle_copy_output(struct demangle_data *ddata, char *buf, return -1; } -static privileged int +ABI static int demangle_failure(char *buf, const char *org, size_t buflen) { demangle_strlcpy(buf, org, buflen); return -1; } -static privileged int +ABI static int demangle(struct demangle_data *ddata, char *buf, const char *org, size_t buflen) { struct vector_str ret_type; @@ -4391,7 +4451,7 @@ demangle(struct demangle_data *ddata, char *buf, const char *org, size_t buflen) * @return bytes of output name or -1 upon error or truncation * @asyncsignalsafe */ -privileged int +ABI int __demangle(char *buf, const char *org, size_t buflen) { struct demangle_data ddata[1]; @@ -4405,7 +4465,7 @@ __demangle(char *buf, const char *org, size_t buflen) * * This means it starts with either "_Z" or "_GLOBAL__I_". */ -privileged int +ABI int __is_mangled(const char *org) { if (!org) diff --git a/libc/intrin/describeallocationtype.c b/libc/intrin/describeallocationtype.c new file mode 100644 index 000000000..4dd69e733 --- /dev/null +++ b/libc/intrin/describeallocationtype.c @@ -0,0 +1,32 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/describeflags.h" +#include "libc/macros.h" +#include "libc/nt/enum/memflags.h" + +static const struct DescribeFlags kNtAllocationTypeFlags[] = { + {kNtMemCommit, "Commit"}, // + {kNtMemReserve, "Reserve"}, // + {kNtMemReset, "Reset"}, // +}; + +const char *_DescribeNtAllocationType(char buf[48], uint32_t x) { + return _DescribeFlags(buf, 48, kNtAllocationTypeFlags, + ARRAYLEN(kNtAllocationTypeFlags), "kNtMem", x); +} diff --git a/libc/intrin/describebacktrace.c b/libc/intrin/describebacktrace.c index 8c92e93eb..7d61f5bc9 100644 --- a/libc/intrin/describebacktrace.c +++ b/libc/intrin/describebacktrace.c @@ -24,13 +24,15 @@ #define N 160 -privileged static bool IsDangerous(const void *ptr) { +#define ABI privileged optimizesize + +ABI static bool IsDangerous(const void *ptr) { if (_weaken(kisdangerous)) return _weaken(kisdangerous)(ptr); return false; } -privileged static char *FormatHex(char *p, unsigned long x) { +ABI static char *FormatHex(char *p, unsigned long x) { int k = x ? (__builtin_clzl(x) ^ 63) + 1 : 1; k = (k + 3) & -4; while (k > 0) @@ -39,8 +41,7 @@ privileged static char *FormatHex(char *p, unsigned long x) { return p; } -privileged dontinstrument const char *_DescribeBacktrace( - char buf[N], const struct StackFrame *fr) { +ABI const char *_DescribeBacktrace(char buf[N], const struct StackFrame *fr) { char *p = buf; char *pe = p + N; bool gotsome = false; diff --git a/libc/intrin/describefdset.c b/libc/intrin/describefdset.c index 62ffccaad..7241b00db 100644 --- a/libc/intrin/describefdset.c +++ b/libc/intrin/describefdset.c @@ -31,6 +31,8 @@ const char *_DescribeFdSet(char buf[N], ssize_t rc, int nfds, fd_set *fds) { if (!fds) return "NULL"; + if (rc == -1) + return "n/a"; if (kisdangerous(fds)) { ksnprintf(buf, N, "%p", fds); return buf; diff --git a/libc/intrin/describeflags.h b/libc/intrin/describeflags.h index 917ef0f46..e63059f0e 100644 --- a/libc/intrin/describeflags.h +++ b/libc/intrin/describeflags.h @@ -8,8 +8,8 @@ struct thatispacked DescribeFlags { const char *name; }; -const char *_DescribeFlags(char *, size_t, const struct DescribeFlags *, - size_t, const char *, unsigned) libcesque; +const char *_DescribeFlags(char *, size_t, const struct DescribeFlags *, size_t, + const char *, unsigned) libcesque; const char *_DescribeArchPrctlCode(char[12], int) libcesque; const char *_DescribeCancelState(char[12], int, int *) libcesque; @@ -27,7 +27,9 @@ const char *_DescribeItimer(char[12], int) libcesque; const char *_DescribeMapFlags(char[64], int) libcesque; const char *_DescribeMapping(char[8], int, int) libcesque; const char *_DescribeMremapFlags(char[30], int) libcesque; +const char *_DescribeMsg(char[16], int) libcesque; const char *_DescribeMsyncFlags(char[48], int) libcesque; +const char *_DescribeNtAllocationType(char[48], uint32_t); const char *_DescribeNtConsoleInFlags(char[256], uint32_t) libcesque; const char *_DescribeNtConsoleOutFlags(char[128], uint32_t) libcesque; const char *_DescribeNtCreationDisposition(uint32_t) libcesque; @@ -84,7 +86,9 @@ const char *_DescribeWhichPrio(char[12], int) libcesque; #define DescribeMapFlags(x) _DescribeMapFlags(alloca(64), x) #define DescribeMapping(x, y) _DescribeMapping(alloca(8), x, y) #define DescribeMremapFlags(x) _DescribeMremapFlags(alloca(30), x) +#define DescribeMsg(x) _DescribeMsg(alloca(16), x) #define DescribeMsyncFlags(x) _DescribeMsyncFlags(alloca(48), x) +#define DescribeNtAllocationType(x) _DescribeNtAllocationType(alloca(48), x) #define DescribeNtConsoleInFlags(x) _DescribeNtConsoleInFlags(alloca(256), x) #define DescribeNtConsoleOutFlags(x) _DescribeNtConsoleOutFlags(alloca(128), x) #define DescribeNtFileAccessFlags(x) _DescribeNtFileAccessFlags(alloca(512), x) diff --git a/libc/intrin/describemapflags.c b/libc/intrin/describemapflags.c index 9367ee083..7d6461b19 100644 --- a/libc/intrin/describemapflags.c +++ b/libc/intrin/describemapflags.c @@ -16,25 +16,29 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/dce.h" #include "libc/intrin/describeflags.h" #include "libc/macros.h" #include "libc/nt/enum/consolemodeflags.h" #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/prot.h" +#define MAP_GROWSDOWN_LINUX 0x00000100 + const char *_DescribeMapFlags(char buf[64], int x) { const struct DescribeFlags kMapFlags[] = { - {MAP_PRIVATE, "PRIVATE"}, // - {MAP_ANONYMOUS, "ANONYMOUS"}, // - {MAP_SHARED, "SHARED"}, // - {MAP_FIXED, "FIXED"}, // - {MAP_FIXED_NOREPLACE, "FIXED_NOREPLACE"}, // - {MAP_HUGETLB, "HUGETLB"}, // - {MAP_CONCEAL, "CONCEAL"}, // - {MAP_LOCKED, "LOCKED"}, // - {MAP_NORESERVE, "NORESERVE"}, // - {MAP_NONBLOCK, "NONBLOCK"}, // - {MAP_POPULATE, "POPULATE"}, // + {MAP_PRIVATE, "PRIVATE"}, // + {MAP_ANONYMOUS, "ANONYMOUS"}, // + {MAP_SHARED, "SHARED"}, // + {MAP_FIXED, "FIXED"}, // + {MAP_FIXED_NOREPLACE, "FIXED_NOREPLACE"}, // + {MAP_HUGETLB, "HUGETLB"}, // + {MAP_CONCEAL, "CONCEAL"}, // + {MAP_LOCKED, "LOCKED"}, // + {MAP_NORESERVE, "NORESERVE"}, // + {MAP_NONBLOCK, "NONBLOCK"}, // + {MAP_POPULATE, "POPULATE"}, // + {IsLinux() ? MAP_GROWSDOWN_LINUX : 0, "GROWSDOWN"}, // }; return _DescribeFlags(buf, 64, kMapFlags, ARRAYLEN(kMapFlags), "MAP_", x); } diff --git a/libc/intrin/describemapping.c b/libc/intrin/describemapping.c index 6510e9848..9371028b8 100644 --- a/libc/intrin/describemapping.c +++ b/libc/intrin/describemapping.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/intrin/describeflags.h" +#include "libc/intrin/maps.h" #include "libc/runtime/memtrack.internal.h" #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/prot.h" @@ -24,12 +25,13 @@ static char DescribeMapType(int flags) { switch (flags & MAP_TYPE) { case MAP_FILE: + if (flags & MAP_NOFORK) + return 'i'; // executable image return '-'; case MAP_PRIVATE: if (flags & MAP_NOFORK) - return 'P'; - else - return 'p'; + return 'w'; // windows memory + return 'p'; case MAP_SHARED: return 's'; default: diff --git a/libc/intrin/describemsg.c b/libc/intrin/describemsg.c new file mode 100644 index 000000000..9cfc5372e --- /dev/null +++ b/libc/intrin/describemsg.c @@ -0,0 +1,36 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/describeflags.h" +#include "libc/macros.h" +#include "libc/sysv/consts/msg.h" + +const char *_DescribeMsg(char buf[16], int x) { + const struct DescribeFlags kMsgFlags[] = { + {MSG_FASTOPEN, "FASTOPEN"}, // order matters + {MSG_OOB, "OOB"}, // + {MSG_PEEK, "PEEK"}, // + {MSG_DONTROUTE, "DONTROUTE"}, // + {MSG_DONTWAIT, "DONTWAIT"}, // + {MSG_WAITALL, "WAITALL"}, // + {MSG_NOSIGNAL, "NOSIGNAL"}, // + {MSG_TRUNC, "TRUNC"}, // + {MSG_CTRUNC, "CTRUNC"}, // + }; + return _DescribeFlags(buf, 16, kMsgFlags, ARRAYLEN(kMsgFlags), "MSG_", x); +} diff --git a/libc/intrin/describeprotflags.c b/libc/intrin/describeprotflags.c index 44008757b..9fad2bd32 100644 --- a/libc/intrin/describeprotflags.c +++ b/libc/intrin/describeprotflags.c @@ -21,6 +21,8 @@ #include "libc/sysv/consts/prot.h" const char *_DescribeProtFlags(char buf[48], int x) { + if (!x) + return "PROT_NONE"; const struct DescribeFlags kProtFlags[] = { {PROT_READ, "READ"}, // {PROT_WRITE, "WRITE"}, // diff --git a/libc/intrin/directmap-metal.c b/libc/intrin/directmap-metal.c index 8ed352fef..30e377da9 100644 --- a/libc/intrin/directmap-metal.c +++ b/libc/intrin/directmap-metal.c @@ -19,7 +19,6 @@ #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/calls/metalfile.internal.h" -#include "libc/intrin/directmap.h" #include "libc/macros.h" #include "libc/runtime/pc.internal.h" #include "libc/str/str.h" @@ -32,19 +31,11 @@ static uint64_t sys_mmap_metal_break; -static struct DirectMap bad_mmap(void) { - struct DirectMap res; - res.addr = (void *)-1; - res.maphandle = -1; - return res; -} - -struct DirectMap sys_mmap_metal(void *vaddr, size_t size, int prot, int flags, - int fd, int64_t off) { +void *sys_mmap_metal(void *vaddr, size_t size, int prot, int flags, int fd, + int64_t off) { /* asan runtime depends on this function */ size_t i; struct mman *mm; - struct DirectMap res; uint64_t addr, faddr = 0, page, e, *pte, *fdpte, *pml4t; mm = __get_mm(); pml4t = __get_pml4t(); @@ -54,18 +45,18 @@ struct DirectMap sys_mmap_metal(void *vaddr, size_t size, int prot, int flags, struct Fd *sfd; struct MetalFile *file; if (off < 0 || fd < 0 || fd >= g_fds.n) - return bad_mmap(); + return MAP_FAILED; sfd = &g_fds.p[fd]; if (sfd->kind != kFdFile) - return bad_mmap(); + return MAP_FAILED; file = (struct MetalFile *)sfd->handle; /* TODO: allow mapping partial page at end of file, if file size not * multiple of page size */ if (off > file->size || size > file->size - off) - return bad_mmap(); + return MAP_FAILED; faddr = (uint64_t)file->base + off; if (faddr % 4096 != 0) - return bad_mmap(); + return MAP_FAILED; } if (!(flags & MAP_FIXED_linux)) { if (!addr) { @@ -88,7 +79,7 @@ struct DirectMap sys_mmap_metal(void *vaddr, size_t size, int prot, int flags, if ((flags & MAP_ANONYMOUS_linux)) { page = __new_page(mm); if (!page) - return bad_mmap(); + return MAP_FAILED; __clear_page(BANE + page); e = page | PAGE_RSRV | PAGE_U; if ((prot & PROT_WRITE)) @@ -114,9 +105,7 @@ struct DirectMap sys_mmap_metal(void *vaddr, size_t size, int prot, int flags, break; } } - res.addr = (void *)addr; - res.maphandle = -1; - return res; + return (void *)addr; } #endif /* __x86_64__ */ diff --git a/libc/intrin/directmap-nt.c b/libc/intrin/directmap-nt.c deleted file mode 100644 index 3cd19da78..000000000 --- a/libc/intrin/directmap-nt.c +++ /dev/null @@ -1,122 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ 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 │ -│ 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/internal.h" -#include "libc/calls/state.internal.h" -#include "libc/errno.h" -#include "libc/intrin/directmap.h" -#include "libc/nt/enum/filemapflags.h" -#include "libc/nt/enum/pageflags.h" -#include "libc/nt/errors.h" -#include "libc/nt/memory.h" -#include "libc/nt/runtime.h" -#include "libc/nt/struct/processmemorycounters.h" -#include "libc/nt/struct/securityattributes.h" -#include "libc/sysv/consts/map.h" -#include "libc/sysv/consts/o.h" -#include "libc/sysv/consts/prot.h" - -textwindows struct DirectMap sys_mmap_nt(void *addr, size_t size, int prot, - int flags, int fd, int64_t off) { - - int64_t handle; - if (flags & MAP_ANONYMOUS) { - handle = kNtInvalidHandleValue; - } else { - handle = g_fds.p[fd].handle; - } - - // mark map handle as inheritable if fork might need it - const struct NtSecurityAttributes *mapsec; - if ((flags & MAP_TYPE) == MAP_SHARED) { - mapsec = &kNtIsInheritable; - } else { - mapsec = 0; - } - - // nt will whine under many circumstances if we change the execute bit - // later using mprotect(). the workaround is to always request execute - // and then virtualprotect() it away until we actually need it. please - // note that open-nt.c always requests an kNtGenericExecute accessmask - int iscow = false; - struct ProtectNt fl; - if (handle != -1) { - if ((flags & MAP_TYPE) != MAP_SHARED) { - // windows has cow pages but they can't propagate across fork() - // that means we only get copy-on-write for the root process :( - fl = (struct ProtectNt){kNtPageExecuteWritecopy, - kNtFileMapCopy | kNtFileMapExecute}; - iscow = true; - } else { - if ((g_fds.p[fd].flags & O_ACCMODE) == O_RDONLY) { - fl = (struct ProtectNt){kNtPageExecuteRead, - kNtFileMapRead | kNtFileMapExecute}; - } else { - fl = (struct ProtectNt){kNtPageExecuteReadwrite, - kNtFileMapWrite | kNtFileMapExecute}; - } - } - } else { - unassert(flags & MAP_ANONYMOUS); - fl = (struct ProtectNt){kNtPageExecuteReadwrite, - kNtFileMapWrite | kNtFileMapExecute}; - } - - int e = errno; - struct DirectMap dm; -TryAgain: - if ((dm.maphandle = CreateFileMapping(handle, mapsec, fl.flags1, - (size + off) >> 32, (size + off), 0))) { - if ((dm.addr = MapViewOfFileEx(dm.maphandle, fl.flags2, off >> 32, off, - size, addr))) { - uint32_t oldprot; - if (VirtualProtect(dm.addr, size, __prot2nt(prot, iscow), &oldprot)) - return dm; - UnmapViewOfFile(dm.addr); - } - CloseHandle(dm.maphandle); - } else if (!(prot & PROT_EXEC) && // - (fl.flags2 & kNtFileMapExecute) && // - GetLastError() == kNtErrorAccessDenied) { - // your file needs to have been O_CREAT'd with exec `mode` bits in - // order to be mapped with executable permission. we always try to - // get execute permission if the kernel will give it to us because - // win32 would otherwise forbid mprotect() from elevating later on - fl.flags2 &= ~kNtFileMapExecute; - switch (fl.flags1) { - case kNtPageExecuteWritecopy: - fl.flags1 = kNtPageWritecopy; - break; - case kNtPageExecuteReadwrite: - fl.flags1 = kNtPageReadwrite; - break; - case kNtPageExecuteRead: - fl.flags1 = kNtPageReadonly; - break; - default: - __builtin_unreachable(); - } - errno = e; - goto TryAgain; - } - - dm.maphandle = kNtInvalidHandleValue; - dm.addr = (void *)(intptr_t)-1; - return dm; -} diff --git a/libc/intrin/directmap.h b/libc/intrin/directmap.h index a3eefc30a..389336a91 100644 --- a/libc/intrin/directmap.h +++ b/libc/intrin/directmap.h @@ -2,19 +2,7 @@ #define COSMOPOLITAN_LIBC_INTRIN_DIRECTMAP_H_ COSMOPOLITAN_C_START_ -struct ProtectNt { - uint32_t flags1; - uint32_t flags2; -}; - -struct DirectMap { - void *addr; - int64_t maphandle; -}; - -struct DirectMap sys_mmap(void *, size_t, int, int, int, int64_t); -struct DirectMap sys_mmap_nt(void *, size_t, int, int, int, int64_t); -struct DirectMap sys_mmap_metal(void *, size_t, int, int, int, int64_t); +void *sys_mmap_metal(void *, size_t, int, int, int, int64_t) libcesque; int sys_munmap_metal(void *, size_t) libcesque; int __prot2nt(int, int) libcesque; diff --git a/libc/intrin/dlopen.c b/libc/intrin/dlopen.c new file mode 100644 index 000000000..3e93f8be3 --- /dev/null +++ b/libc/intrin/dlopen.c @@ -0,0 +1,34 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 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/thread/posixthread.internal.h" +#include "libc/thread/thread.h" + +static pthread_mutex_t __dlopen_lock_obj = PTHREAD_MUTEX_INITIALIZER; + +void __dlopen_lock(void) { + _pthread_mutex_lock(&__dlopen_lock_obj); +} + +void __dlopen_unlock(void) { + _pthread_mutex_unlock(&__dlopen_lock_obj); +} + +void __dlopen_wipe(void) { + _pthread_mutex_wipe_np(&__dlopen_lock_obj); +} diff --git a/libc/intrin/fds.c b/libc/intrin/fds.c index d2883cccf..7fcfe983d 100644 --- a/libc/intrin/fds.c +++ b/libc/intrin/fds.c @@ -44,15 +44,13 @@ #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/prot.h" #include "libc/thread/thread.h" - -#define OPEN_MAX 16 +#include "libc/thread/tls.h" #ifdef __x86_64__ __static_yoink("_init_fds"); #endif struct Fds g_fds; -static struct Fd g_fds_static[OPEN_MAX]; static bool TokAtoi(const char **str, long *res) { int c, d; @@ -86,19 +84,14 @@ static textwindows void SetupWinStd(struct Fds *fds, int i, uint32_t x) { } textstartup void __init_fds(int argc, char **argv, char **envp) { + struct Fds *fds; fds = &g_fds; fds->n = 4; atomic_store_explicit(&fds->f, 3, memory_order_relaxed); - if (_weaken(_extend)) { - fds->p = fds->e = (void *)kMemtrackFdsStart; - fds->e = - _weaken(_extend)(fds->p, fds->n * sizeof(*fds->p), fds->e, MAP_PRIVATE, - kMemtrackFdsStart + kMemtrackFdsSize); - } else { - fds->p = g_fds_static; - fds->e = g_fds_static + OPEN_MAX; - } + fds->p = fds->e = (void *)kMemtrackFdsStart; + fds->e = _extend(fds->p, fds->n * sizeof(*fds->p), fds->e, MAP_PRIVATE, + kMemtrackFdsStart + kMemtrackFdsSize); // inherit standard i/o file descriptors if (IsMetal()) { @@ -129,6 +122,7 @@ textstartup void __init_fds(int argc, char **argv, char **envp) { if (IsWindows()) { const char *fdspec; if ((fdspec = getenv("_COSMO_FDS_V2"))) { + char *smaddr = 0; unsetenv("_COSMO_FDS"); unsetenv("_COSMO_FDS_V2"); for (;;) { @@ -151,8 +145,7 @@ textstartup void __init_fds(int argc, char **argv, char **envp) { break; if (!TokAtoi(&fdspec, &protocol)) break; - if (_weaken(__ensurefds_unlocked)) - _weaken(__ensurefds_unlocked)(fd); + __ensurefds_unlocked(fd); struct Fd *f = fds->p + fd; if (f->handle && f->handle != -1 && f->handle != handle) { CloseHandle(f->handle); @@ -171,8 +164,13 @@ textstartup void __init_fds(int argc, char **argv, char **envp) { if (shand) { struct Map *map; struct CursorShared *shared; + if (!smaddr) { + smaddr = __maps_randaddr(); + } else { + smaddr += 65536; + } if ((shared = MapViewOfFileEx(shand, kNtFileMapWrite, 0, 0, - sizeof(struct CursorShared), 0))) { + sizeof(struct CursorShared), smaddr))) { if ((f->cursor = _mapanon(sizeof(struct Cursor)))) { f->cursor->shared = shared; if ((map = __maps_alloc())) { @@ -182,7 +180,9 @@ textstartup void __init_fds(int argc, char **argv, char **envp) { map->prot = PROT_READ | PROT_WRITE; map->flags = MAP_SHARED | MAP_ANONYMOUS; map->hand = shand; + __maps_lock(); __maps_insert(map); + __maps_unlock(); } } } diff --git a/libc/intrin/fds_lock.c b/libc/intrin/fds_lock.c index c32367d85..1e1ddcc32 100644 --- a/libc/intrin/fds_lock.c +++ b/libc/intrin/fds_lock.c @@ -17,12 +17,13 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/state.internal.h" +#include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" void __fds_lock(void) { - pthread_mutex_lock(&__fds_lock_obj); + _pthread_mutex_lock(&__fds_lock_obj); } void __fds_unlock(void) { - pthread_mutex_unlock(&__fds_lock_obj); + _pthread_mutex_unlock(&__fds_lock_obj); } diff --git a/libc/calls/getcontext.S b/libc/intrin/getcontext.S similarity index 97% rename from libc/calls/getcontext.S rename to libc/intrin/getcontext.S index a05f5c83c..bdfaded97 100644 --- a/libc/calls/getcontext.S +++ b/libc/intrin/getcontext.S @@ -26,7 +26,9 @@ // @see setcontext() .ftrace1 getcontext: + beg .ftrace2 -#include "libc/calls/getcontext.inc" +#include "libc/intrin/getcontext.inc" jmp __getcontextsig + end .endfn getcontext,globl diff --git a/libc/calls/getcontext.inc b/libc/intrin/getcontext.inc similarity index 99% rename from libc/calls/getcontext.inc rename to libc/intrin/getcontext.inc index ea0a8b4a8..bbc452658 100644 --- a/libc/calls/getcontext.inc +++ b/libc/intrin/getcontext.inc @@ -34,6 +34,7 @@ mov %rbp,120(%rdi) mov %rbx,128(%rdi) mov %rdx,136(%rdi) + mov %rax,144(%rdi) mov %rcx,152(%rdi) lea 8(%rsp),%rax mov %rax,160(%rdi) // rsp = caller's rsp diff --git a/libc/intrin/getmainstack.c b/libc/intrin/getmainstack.c index 5aa21a6d6..afcf18e5a 100644 --- a/libc/intrin/getmainstack.c +++ b/libc/intrin/getmainstack.c @@ -17,16 +17,13 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/struct/rlimit.h" -#include "libc/calls/struct/rlimit.internal.h" -#include "libc/dce.h" #include "libc/intrin/getauxval.h" -#include "libc/intrin/kprintf.h" #include "libc/intrin/maps.h" +#include "libc/intrin/rlimit.h" #include "libc/macros.h" #include "libc/runtime/runtime.h" +#include "libc/stdio/sysparam.h" #include "libc/sysv/consts/auxv.h" -#include "libc/sysv/consts/rlim.h" -#include "libc/sysv/consts/rlimit.h" // Hack for guessing boundaries of _start()'s stack // @@ -91,12 +88,9 @@ static uintptr_t __get_main_top(int pagesz) { } static size_t __get_stack_size(int pagesz, uintptr_t start, uintptr_t top) { - size_t size, max = 8 * 1024 * 1024; - struct rlimit rlim = {RLIM_INFINITY}; - sys_getrlimit(RLIMIT_STACK, &rlim); - if ((size = rlim.rlim_cur) > max) - size = max; - return MAX(ROUNDUP(size, pagesz), ROUNDUP(top - start, pagesz)); + size_t stacksz = __rlimit_stack_get().rlim_cur; + stacksz = MIN(stacksz, 1024ul * 1024 * 1024 * 1024); + return MAX(ROUNDDOWN(stacksz, pagesz), ROUNDUP(top - start, pagesz)); } /** diff --git a/libc/intrin/getminsigstksz.c b/libc/intrin/getminsigstksz.c index 2718aa13d..9b746e279 100644 --- a/libc/intrin/getminsigstksz.c +++ b/libc/intrin/getminsigstksz.c @@ -16,18 +16,47 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/struct/siginfo.h" +#include "libc/calls/ucontext.h" +#include "libc/dce.h" #include "libc/intrin/getauxval.h" -#include "libc/macros.h" #include "libc/runtime/runtime.h" #include "libc/sysv/consts/auxv.h" #include "libc/sysv/consts/ss.h" long __get_minsigstksz(void) { - struct AuxiliaryValue x; - x = __getauxval(AT_MINSIGSTKSZ); - if (x.isfound) { - return MAX(_MINSIGSTKSZ, x.value); + struct AuxiliaryValue av; + av = __getauxval(AT_MINSIGSTKSZ); + if (av.isfound) { + long res = av.value; + if (!IsLinux()) + res += sizeof(struct ucontext) + sizeof(struct siginfo) + 128; + if (res < _MINSIGSTKSZ) + res = _MINSIGSTKSZ; + return res; } else { + // _MINSIGSTKSZ takes these things into consideration: + // + // 1. The platform definition of MINSIGSTKSZ. This will probably be + // enforced by the kernel when calling sys_sigaltstack(). On ARM + // platforms this might be several kilobytes larger than x86. On + // Linux they really want you to use AT_MINSIGSTKSZ instead. The + // kernel should ideally set this to be the number of bytes that + // get subtracted from the stack pointer when delivering signals + // meaning that if you use this for a stack size your handler is + // called successfully but if it uses the stack then it'll crash + // + // 2. Cosmo sigenter overhead. On non-Linux OSes the kernel calls a + // trampoline in the libc runtime, which translates the platform + // specific signal frame to the Linux memory layout. It means we + // need to push ~1024 extra bytes on the stack to call a handler + // + // 3. Sanity testing. Assume we use sysconf(_SC_MINSIGSTKSZ) + 2048 + // as our stack size (see stackoverflow1_test.c). Then we should + // have enough room to use kprintf() from our signal handler. If + // that isn't the case, then this should be increased a bit more + // noting that if 1024 is used then kprintf should print refusal + // return _MINSIGSTKSZ; } } diff --git a/libc/intrin/getsafesize.greg.c b/libc/intrin/getsafesize.greg.c index 83a772e8a..5a6d9123b 100644 --- a/libc/intrin/getsafesize.greg.c +++ b/libc/intrin/getsafesize.greg.c @@ -17,12 +17,11 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "ape/sections.internal.h" -#include "libc/intrin/kprintf.h" #include "libc/runtime/memtrack.internal.h" +#include "libc/runtime/runtime.h" #include "libc/runtime/stack.h" #include "libc/thread/posixthread.internal.h" #include "libc/thread/tls.h" -#include "libc/thread/tls2.internal.h" /** * Computes safer buffer size for alloca(). @@ -32,18 +31,19 @@ * @return number of bytes to use for your buffer, or negative if the * allocation would likely cause a stack overflow */ -privileged long __get_safe_size(long want, long extraspace) { +privileged optimizesize long __get_safe_size(long want, long extraspace) { if (!__tls_enabled) return want; struct PosixThread *pt; struct CosmoTib *tib = __get_tls_privileged(); long bottom, sp = GetStackPointer(); - if ((char *)sp >= tib->tib_sigstack_addr && - (char *)sp <= tib->tib_sigstack_addr + tib->tib_sigstack_size) { + if (sp >= (long)tib->tib_sigstack_addr && + sp < (long)tib->tib_sigstack_addr + tib->tib_sigstack_size) { bottom = (long)tib->tib_sigstack_addr; } else if ((pt = (struct PosixThread *)tib->tib_pthread) && - pt->pt_attr.__stacksize) { - bottom = (long)pt->pt_attr.__stackaddr + pt->pt_attr.__guardsize; + sp >= (long)pt->pt_attr.__stackaddr && + sp < (long)pt->pt_attr.__stackaddr + pt->pt_attr.__stacksize) { + bottom = (long)pt->pt_attr.__stackaddr; } else { return want; } diff --git a/libc/intrin/gettid.c b/libc/intrin/gettid.c index 6c5b0c9de..48c7c9e42 100644 --- a/libc/intrin/gettid.c +++ b/libc/intrin/gettid.c @@ -39,7 +39,7 @@ int gettid(void) { int tid; if (VERY_LIKELY(__tls_enabled && !__vforked)) { - tid = atomic_load_explicit(&__get_tls()->tib_tid, memory_order_acquire); + tid = atomic_load_explicit(&__get_tls()->tib_ptid, memory_order_relaxed); if (VERY_LIKELY(tid > 0)) return tid; } diff --git a/libc/calls/isqemu.c b/libc/intrin/isqemu.c similarity index 100% rename from libc/calls/isqemu.c rename to libc/intrin/isqemu.c diff --git a/libc/intrin/itimer.c b/libc/intrin/itimer.c new file mode 100644 index 000000000..4d1825396 --- /dev/null +++ b/libc/intrin/itimer.c @@ -0,0 +1,43 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 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/thread/itimer.h" +#include "libc/str/str.h" +#include "libc/thread/posixthread.internal.h" + +struct IntervalTimer __itimer = { + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, +}; + +textwindows void __itimer_lock(void) { + _pthread_mutex_lock(&__itimer.lock); +} + +textwindows void __itimer_unlock(void) { + _pthread_mutex_unlock(&__itimer.lock); +} + +textwindows void __itimer_wipe_and_reset(void) { + // timers aren't inherited by forked subprocesses + bzero(&__itimer.it, sizeof(__itimer.it)); + _pthread_mutex_wipe_np(&__itimer.lock); + bzero(&__itimer.cond, sizeof(__itimer.cond)); + __itimer.thread = 0; + __itimer.once = 0; +} diff --git a/libc/intrin/itoa16.c b/libc/intrin/itoa16.c new file mode 100644 index 000000000..003aba59c --- /dev/null +++ b/libc/intrin/itoa16.c @@ -0,0 +1,37 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 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/fmt/internal.h" + +__msabi textwindows dontinstrument char16_t *__itoa16(char16_t p[21], + uint64_t x) { + char t; + size_t a, b, i = 0; + do { + p[i++] = x % 10 + '0'; + x = x / 10; + } while (x > 0); + if (i) { + for (a = 0, b = i - 1; a < b; ++a, --b) { + t = p[a]; + p[a] = p[b]; + p[b] = t; + } + } + return p + i; +} diff --git a/libc/intrin/kisdangerous.c b/libc/intrin/kisdangerous.c new file mode 100644 index 000000000..2672eae0d --- /dev/null +++ b/libc/intrin/kisdangerous.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/kprintf.h" +#include "libc/intrin/maps.h" +#include "libc/runtime/runtime.h" + +privileged optimizesize bool32 kisdangerous(const void *addr) { + bool32 res = true; + __maps_lock(); + if (__maps.maps) { + struct Map *map; + if ((map = __maps_floor(addr))) + if ((const char *)addr >= map->addr && + (const char *)addr < + map->addr + ((map->size + __pagesize - 1) & -__pagesize)) + res = false; + } else { + res = false; + } + __maps_unlock(); + return res; +} diff --git a/libc/intrin/kprintf.greg.c b/libc/intrin/kprintf.greg.c index ee9f6a539..a303723c5 100644 --- a/libc/intrin/kprintf.greg.c +++ b/libc/intrin/kprintf.greg.c @@ -40,9 +40,11 @@ #include "libc/nt/enum/fileflagandattributes.h" #include "libc/nt/enum/filesharemode.h" #include "libc/nt/errors.h" +#include "libc/nt/events.h" #include "libc/nt/files.h" #include "libc/nt/process.h" #include "libc/nt/runtime.h" +#include "libc/nt/struct/overlapped.h" #include "libc/nt/thunk/msabi.h" #include "libc/runtime/internal.h" #include "libc/runtime/memtrack.internal.h" @@ -63,10 +65,11 @@ #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/prot.h" #include "libc/thread/tls.h" -#include "libc/thread/tls2.internal.h" #include "libc/vga/vga.internal.h" #include "libc/wctype.h" +#define ABI privileged optimizesize + #define STACK_ERROR "kprintf error: stack is about to overflow\n" #define KGETINT(x, va, t, s) \ @@ -113,10 +116,13 @@ } // clang-format off +__msabi extern typeof(CloseHandle) *const __imp_CloseHandle; +__msabi extern typeof(CreateEvent) *const __imp_CreateEventW; __msabi extern typeof(CreateFile) *const __imp_CreateFileW; __msabi extern typeof(DuplicateHandle) *const __imp_DuplicateHandle; __msabi extern typeof(GetEnvironmentVariable) *const __imp_GetEnvironmentVariableW; __msabi extern typeof(GetLastError) *const __imp_GetLastError; +__msabi extern typeof(GetOverlappedResult) *const __imp_GetOverlappedResult; __msabi extern typeof(GetStdHandle) *const __imp_GetStdHandle; __msabi extern typeof(SetLastError) *const __imp_SetLastError; __msabi extern typeof(WriteFile) *const __imp_WriteFile; @@ -154,23 +160,7 @@ __funline bool kischarmisaligned(const char *p, signed char t) { return false; } -privileged bool32 kisdangerous(const void *addr) { - bool32 res = true; - __maps_lock(); - if (__maps.maps) { - struct Map *map; - if ((map = __maps_floor(addr))) - if ((const char *)addr >= map->addr && - (const char *)addr < map->addr + map->size) - res = false; - } else { - res = false; - } - __maps_unlock(); - return res; -} - -privileged static void klogclose(long fd) { +ABI static void klogclose(long fd) { #ifdef __x86_64__ long ax = __NR_close; asm volatile("syscall" @@ -187,7 +177,7 @@ privileged static void klogclose(long fd) { #endif } -privileged static long klogfcntl(long fd, long cmd, long arg) { +ABI static long klogfcntl(long fd, long cmd, long arg) { #ifdef __x86_64__ char cf; long ax = __NR_fcntl; @@ -219,7 +209,7 @@ privileged static long klogfcntl(long fd, long cmd, long arg) { #endif } -privileged static long klogopen(const char *path) { +ABI static long klogopen(const char *path) { long dirfd = AT_FDCWD; long flags = O_WRONLY | O_CREAT | O_APPEND; long mode = 0600; @@ -258,7 +248,7 @@ privileged static long klogopen(const char *path) { } // returns log handle or -1 if logging shouldn't happen -privileged long kloghandle(void) { +ABI long kloghandle(void) { // kprintf() needs to own a file descriptor in case apps closes stderr // our close() and dup() implementations will trigger this initializer // to minimize a chance that the user accidentally closes their logger @@ -283,7 +273,7 @@ privileged long kloghandle(void) { hand = __imp_CreateFileW( path, kNtFileAppendData, kNtFileShareRead | kNtFileShareWrite | kNtFileShareDelete, 0, - kNtOpenAlways, kNtFileAttributeNormal, 0); + kNtOpenAlways, kNtFileAttributeNormal | kNtFileFlagOverlapped, 0); } else { hand = -1; // KPRINTF_LOG was empty string or too long } @@ -337,7 +327,7 @@ privileged long kloghandle(void) { } #ifdef __x86_64__ -privileged void _klog_serial(const char *b, size_t n) { +ABI void _klog_serial(const char *b, size_t n) { size_t i; uint16_t dx; unsigned char al; @@ -357,21 +347,28 @@ privileged void _klog_serial(const char *b, size_t n) { } #endif /* __x86_64__ */ -privileged void klog(const char *b, size_t n) { +ABI void klog(const char *b, size_t n) { #ifdef __x86_64__ - int e; long h; uint32_t wrote; long rax, rdi, rsi, rdx; - if ((h = kloghandle()) == -1) { + if ((h = kloghandle()) == -1) return; - } if (IsWindows()) { - e = __imp_GetLastError(); - if (!__imp_WriteFile(h, b, n, &wrote, 0)) { - __imp_SetLastError(e); - __klog_handle = 0; + bool32 ok; + intptr_t ev; + int e = __imp_GetLastError(); + if ((ev = __imp_CreateEventW(0, 0, 0, 0))) { + struct NtOverlapped overlap = {.hEvent = ev}; + ok = !!__imp_WriteFile(h, b, n, 0, &overlap); + if (!ok && __imp_GetLastError() == kNtErrorIoPending) + ok = true; + ok &= !!__imp_GetOverlappedResult(h, &overlap, &wrote, true); + if (!ok) + __klog_handle = 0; + __imp_CloseHandle(ev); } + __imp_SetLastError(e); } else if (IsMetal()) { if (_weaken(_klog_vga)) { _weaken(_klog_vga)(b, n); @@ -407,14 +404,14 @@ privileged void klog(const char *b, size_t n) { #endif } -privileged static size_t kformat(char *b, size_t n, const char *fmt, - va_list va) { +ABI static size_t kformat(char *b, size_t n, const char *fmt, va_list va) { int si; wint_t t, u; + char *cxxbuf; const char *abet; signed char type; const char *s, *f; - char cxxbuf[3000]; + int cxxbufsize = 0; struct CosmoTib *tib; unsigned long long x; unsigned i, j, m, rem, sign, hash, cols, prec; @@ -564,7 +561,7 @@ privileged static size_t kformat(char *b, size_t n, const char *fmt, tib = __tls_enabled ? __get_tls_privileged() : 0; if (!(tib && (tib->tib_flags & TIB_FLAG_VFORKED))) { if (tib) { - x = atomic_load_explicit(&tib->tib_tid, memory_order_relaxed); + x = atomic_load_explicit(&tib->tib_ptid, memory_order_relaxed); } else { x = __pid; } @@ -758,13 +755,25 @@ privileged static size_t kformat(char *b, size_t n, const char *fmt, x = va_arg(va, intptr_t); if (_weaken(__symtab) && *_weaken(__symtab) && (idx = _weaken(__get_symbol)(0, x)) != -1) { - /* if (p + 1 <= e) */ - /* *p++ = '&'; */ s = (*_weaken(__symtab))->name_base + (*_weaken(__symtab))->names[idx]; - if (_weaken(__is_mangled) && _weaken(__is_mangled)(s) && - _weaken(__demangle)(cxxbuf, s, sizeof(cxxbuf)) != -1) - s = cxxbuf; +#pragma GCC push_options +#pragma GCC diagnostic ignored "-Walloca-larger-than=" + // decipher c++ symbols if there's enough stack memory + // stack size requirement assumes max_depth's still 20 + if (_weaken(__demangle) && // + _weaken(__is_mangled) && // + _weaken(__is_mangled)(s)) { + if (!cxxbufsize) + if ((cxxbufsize = __get_safe_size(8192, 8192)) >= 512) { + cxxbuf = alloca(cxxbufsize); + CheckLargeStackAllocation(cxxbuf, sizeof(cxxbufsize)); + } + if (cxxbufsize >= 512) + if (_weaken(__demangle)(cxxbuf, s, cxxbufsize) != -1) + s = cxxbuf; + } +#pragma GCC pop_options goto FormatString; } base = 4; @@ -1020,7 +1029,7 @@ privileged static size_t kformat(char *b, size_t n, const char *fmt, * @asyncsignalsafe * @vforksafe */ -privileged size_t ksnprintf(char *b, size_t n, const char *fmt, ...) { +ABI size_t ksnprintf(char *b, size_t n, const char *fmt, ...) { size_t m; va_list v; va_start(v, fmt); @@ -1039,7 +1048,7 @@ privileged size_t ksnprintf(char *b, size_t n, const char *fmt, ...) { * @asyncsignalsafe * @vforksafe */ -privileged size_t kvsnprintf(char *b, size_t n, const char *fmt, va_list v) { +ABI size_t kvsnprintf(char *b, size_t n, const char *fmt, va_list v) { return kformat(b, n, fmt, v); } @@ -1050,10 +1059,10 @@ privileged size_t kvsnprintf(char *b, size_t n, const char *fmt, va_list v) { * @asyncsignalsafe * @vforksafe */ -privileged void kvprintf(const char *fmt, va_list v) { +ABI void kvprintf(const char *fmt, va_list v) { #pragma GCC push_options #pragma GCC diagnostic ignored "-Walloca-larger-than=" - long size = __get_safe_size(8000, 8000); + long size = __get_safe_size(8192, 2048); if (size < 80) { klog(STACK_ERROR, sizeof(STACK_ERROR) - 1); return; @@ -1136,7 +1145,7 @@ privileged void kvprintf(const char *fmt, va_list v) { * @asyncsignalsafe * @vforksafe */ -privileged void kprintf(const char *fmt, ...) { +ABI void kprintf(const char *fmt, ...) { // system call support runtime depends on this function // function tracing runtime depends on this function // asan runtime depends on this function diff --git a/libc/intrin/localtime_lock.c b/libc/intrin/localtime_lock.c new file mode 100644 index 000000000..bbc0a04d1 --- /dev/null +++ b/libc/intrin/localtime_lock.c @@ -0,0 +1,34 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 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/thread/posixthread.internal.h" +#include "third_party/tz/lock.h" + +static pthread_mutex_t __localtime_lock_obj = PTHREAD_MUTEX_INITIALIZER; + +void __localtime_lock(void) { + _pthread_mutex_lock(&__localtime_lock_obj); +} + +void __localtime_unlock(void) { + _pthread_mutex_unlock(&__localtime_lock_obj); +} + +void __localtime_wipe(void) { + _pthread_mutex_wipe_np(&__localtime_lock_obj); +} diff --git a/libc/intrin/lockless.h b/libc/intrin/lockless.h new file mode 100644 index 000000000..7855f16c2 --- /dev/null +++ b/libc/intrin/lockless.h @@ -0,0 +1,50 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_LOCKLESS_H_ +#define COSMOPOLITAN_LIBC_INTRIN_LOCKLESS_H_ +#include "libc/atomic.h" +#include "libc/intrin/atomic.h" +COSMOPOLITAN_C_START_ + +// lockless memory transactions +// +// - one writer +// - many readers +// - generation is monotonic +// - even numbers mean memory is ready +// - odd numbers mean memory is actively being changed +// - always use acquire semantics inside your read transaction +// +// let's say you want to be able to atomically read and write to 128-bit +// values, but you've only got a 64-bit system. if you expect that it'll +// frequently written, then you should use a mutex. but if you expect it +// to be frequently read and rarely written, then it's possible to do it +// without a mutex; in fact you don't even need the x86 lock instruction +// prefix; all that is required is a series of carefully ordered mov ops +// which are designed to exploit the strong ordering of the architecture + +static inline unsigned lockless_write_begin(atomic_uint* genptr) { + unsigned gen = atomic_load_explicit(genptr, memory_order_acquire); + atomic_store_explicit(genptr, gen + 1, memory_order_release); + return gen; +} + +static inline void lockless_write_end(atomic_uint* genptr, unsigned gen) { + atomic_store_explicit(genptr, gen + 2, memory_order_release); +} + +static inline unsigned lockless_read_begin(atomic_uint* genptr) { + return atomic_load_explicit(genptr, memory_order_acquire); +} + +static inline bool lockless_read_end(atomic_uint* genptr, unsigned* want) { + unsigned gen1 = *want; + unsigned gen2 = atomic_load_explicit(genptr, memory_order_acquire); + unsigned is_being_actively_changed = gen1 & 1; + unsigned we_lost_race_with_writers = gen1 ^ gen2; + if (!(is_being_actively_changed | we_lost_race_with_writers)) + return true; + *want = gen2; + return false; +} + +COSMOPOLITAN_C_END_ +#endif /* COSMOPOLITAN_LIBC_INTRIN_LOCKLESS_H_ */ diff --git a/libc/intrin/maps.c b/libc/intrin/maps.c index ede90e395..7f74960e1 100644 --- a/libc/intrin/maps.c +++ b/libc/intrin/maps.c @@ -19,21 +19,27 @@ #include "libc/intrin/maps.h" #include "ape/sections.internal.h" #include "libc/calls/state.internal.h" +#include "libc/calls/syscall-sysv.internal.h" +#include "libc/cosmo.h" #include "libc/dce.h" #include "libc/intrin/describebacktrace.h" #include "libc/intrin/dll.h" -#include "libc/intrin/kprintf.h" #include "libc/intrin/maps.h" +#include "libc/macros.h" +#include "libc/nexgen32e/rdtsc.h" #include "libc/runtime/runtime.h" #include "libc/runtime/stack.h" -#include "libc/sysv/consts/auxv.h" +#include "libc/sysv/consts/map.h" #include "libc/sysv/consts/prot.h" #include "libc/thread/lock.h" +#include "libc/thread/tls.h" #ifdef __x86_64__ __static_yoink("_init_maps"); #endif +#define ABI privileged optimizespeed + struct Maps __maps; void __maps_add(struct Map *map) { @@ -51,74 +57,109 @@ void __maps_stack(char *stackaddr, int pagesz, int guardsize, size_t stacksize, __maps.stack.addr = stackaddr + guardsize; __maps.stack.size = stacksize - guardsize; __maps.stack.prot = stackprot; - __maps.stack.hand = -1; + __maps.stack.hand = MAPS_SUBREGION; + __maps.stack.flags = MAP_PRIVATE | MAP_ANONYMOUS; __maps_adder(&__maps.stack, pagesz); if (guardsize) { __maps.guard.addr = stackaddr; __maps.guard.size = guardsize; - __maps.guard.prot = PROT_NONE; + __maps.guard.prot = PROT_NONE | PROT_GUARD; __maps.guard.hand = stackhand; + __maps.guard.flags = MAP_PRIVATE | MAP_ANONYMOUS; __maps_adder(&__maps.guard, pagesz); + } else { + __maps.stack.hand = stackhand; } } void __maps_init(void) { int pagesz = __pagesize; + // initialize lemur64 + __maps.rand = 2131259787901769494; + __maps.rand ^= kStartTsc; + + // these static map objects avoid mandatory mmap() in __maps_alloc() + // they aren't actually needed for bootstrapping this memory manager + for (int i = 0; i < ARRAYLEN(__maps.spool); ++i) + __maps_free(&__maps.spool[i]); + // record _start() stack mapping if (!IsWindows()) { - struct AddrSize stack; - stack = __get_main_stack(); - __maps_stack(stack.addr, pagesz, 0, stack.size, (uintptr_t)ape_stack_prot, - 0); + + // linux v4.12+ reserves 1mb of guard space beneath rlimit_stack + // https://lwn.net/Articles/725832/. if we guess too small, then + // slackmap will create a bunch of zombie stacks in __print_maps + // to coverup the undisclosed memory but no cost if we guess big + size_t guardsize = 1024 * 1024; + guardsize += __pagesize - 1; + guardsize &= -__pagesize; + + // track the main stack region that the os gave to start earlier + struct AddrSize stack = __get_main_stack(); + __maps_stack(stack.addr - guardsize, pagesz, guardsize, + guardsize + stack.size, (uintptr_t)ape_stack_prot, 0); } // record .text and .data mappings - static struct Map text, data; - text.addr = (char *)__executable_start; - text.size = _etext - __executable_start; - text.prot = PROT_READ | PROT_EXEC; + __maps_track((char *)__executable_start, _etext - __executable_start, + PROT_READ | PROT_EXEC, MAP_NOFORK); uintptr_t ds = ((uintptr_t)_etext + pagesz - 1) & -pagesz; - if (ds < (uintptr_t)_end) { - data.addr = (char *)ds; - data.size = (uintptr_t)_end - ds; - data.prot = PROT_READ | PROT_WRITE; - __maps_adder(&data, pagesz); - } - __maps_adder(&text, pagesz); + if (ds < (uintptr_t)_end) + __maps_track((char *)ds, (uintptr_t)_end - ds, PROT_READ | PROT_WRITE, + MAP_NOFORK); } -privileged bool __maps_lock(void) { +bool __maps_held(void) { + return !__tls_enabled || (__get_tls()->tib_flags & TIB_FLAG_VFORKED) || + MUTEX_OWNER( + atomic_load_explicit(&__maps.lock.word, memory_order_relaxed)) == + atomic_load_explicit(&__get_tls()->tib_ptid, memory_order_relaxed); +} + +bool __maps_reentrant(void) { + return __tls_enabled && !(__get_tls()->tib_flags & TIB_FLAG_VFORKED) && + MUTEX_OWNER( + atomic_load_explicit(&__maps.lock.word, memory_order_relaxed)) == + atomic_load_explicit(&__get_tls()->tib_ptid, memory_order_relaxed); +} + +ABI void __maps_lock(void) { int me; uint64_t word, lock; struct CosmoTib *tib; if (!__tls_enabled) - return false; + return; if (!(tib = __get_tls_privileged())) - return false; + return; if (tib->tib_flags & TIB_FLAG_VFORKED) - return false; - me = atomic_load_explicit(&tib->tib_tid, memory_order_acquire); - if (me <= 0) - return false; - word = atomic_load_explicit(&__maps.lock, memory_order_relaxed); + return; + me = atomic_load_explicit(&tib->tib_ptid, memory_order_relaxed); + word = 0; + lock = MUTEX_LOCK(word); + lock = MUTEX_SET_OWNER(lock, me); + if (atomic_compare_exchange_strong_explicit(&__maps.lock.word, &word, lock, + memory_order_acquire, + memory_order_relaxed)) + return; + word = atomic_load_explicit(&__maps.lock.word, memory_order_relaxed); for (;;) { if (MUTEX_OWNER(word) == me) { if (atomic_compare_exchange_weak_explicit( - &__maps.lock, &word, MUTEX_INC_DEPTH(word), memory_order_relaxed, - memory_order_relaxed)) - return true; + &__maps.lock.word, &word, MUTEX_INC_DEPTH(word), + memory_order_relaxed, memory_order_relaxed)) + return; continue; } word = 0; lock = MUTEX_LOCK(word); lock = MUTEX_SET_OWNER(lock, me); - if (atomic_compare_exchange_weak_explicit(&__maps.lock, &word, lock, + if (atomic_compare_exchange_weak_explicit(&__maps.lock.word, &word, lock, memory_order_acquire, memory_order_relaxed)) - return false; + return; for (;;) { - word = atomic_load_explicit(&__maps.lock, memory_order_relaxed); + word = atomic_load_explicit(&__maps.lock.word, memory_order_relaxed); if (MUTEX_OWNER(word) == me) break; if (!word) @@ -127,8 +168,7 @@ privileged bool __maps_lock(void) { } } -privileged void __maps_unlock(void) { - int me; +ABI void __maps_unlock(void) { uint64_t word; struct CosmoTib *tib; if (!__tls_enabled) @@ -137,19 +177,16 @@ privileged void __maps_unlock(void) { return; if (tib->tib_flags & TIB_FLAG_VFORKED) return; - me = atomic_load_explicit(&tib->tib_tid, memory_order_acquire); - if (me <= 0) - return; - word = atomic_load_explicit(&__maps.lock, memory_order_relaxed); + word = atomic_load_explicit(&__maps.lock.word, memory_order_relaxed); for (;;) { - if (MUTEX_DEPTH(word)) { + if (MUTEX_DEPTH(word)) if (atomic_compare_exchange_weak_explicit( - &__maps.lock, &word, MUTEX_DEC_DEPTH(word), memory_order_relaxed, - memory_order_relaxed)) + &__maps.lock.word, &word, MUTEX_DEC_DEPTH(word), + memory_order_relaxed, memory_order_relaxed)) break; - } - if (atomic_compare_exchange_weak_explicit( - &__maps.lock, &word, 0, memory_order_release, memory_order_relaxed)) + if (atomic_compare_exchange_weak_explicit(&__maps.lock.word, &word, 0, + memory_order_release, + memory_order_relaxed)) break; } } diff --git a/libc/intrin/maps.h b/libc/intrin/maps.h index 33048ca03..86b1f2f55 100644 --- a/libc/intrin/maps.h +++ b/libc/intrin/maps.h @@ -3,10 +3,29 @@ #include "libc/intrin/atomic.h" #include "libc/intrin/tree.h" #include "libc/runtime/runtime.h" -#include "libc/thread/tls2.internal.h" COSMOPOLITAN_C_START_ -#define MAPS_RETRY ((void *)-1) +/* size of dynamic memory that is used internally by your memory manager */ +#define MAPS_SIZE 65536 + +/* when map->hand is MAPS_RESERVATION it means mmap() is transactionally + reserving address space it is in the process of requesting from win32 */ +#define MAPS_RESERVATION -2 + +/* when map->hand is MAPS_SUBREGION it means that an allocation has been + broken into multiple fragments by mprotect(). the first fragment must + be set to MAPS_VIRTUAL or your CreateFileMapping() handle. your frags + must be perfectly contiguous in memory and should have the same flags */ +#define MAPS_SUBREGION -3 + +/* indicates an allocation was created by VirtualAlloc() and so munmap() + must call VirtualFree() when destroying it. use it on the hand field. */ +#define MAPS_VIRTUAL -4 + +/* if this is used on MAP_PRIVATE memory, then it's assumed to be memory + that win32 allocated, e.g. a CreateThread() stack. if this is used on + MAP_FILE memory, then it's assumed to be part of the executable image */ +#define MAP_NOFORK 0x10000000 #define MAP_TREE_CONTAINER(e) TREE_CONTAINER(struct Map, tree, e) @@ -14,8 +33,8 @@ struct Map { char *addr; /* granule aligned */ size_t size; /* must be nonzero */ int64_t off; /* ignore for anon */ - int prot; /* memory protects */ int flags; /* memory map flag */ + short prot; /* memory protects */ bool iscow; /* windows nt only */ bool readonlyfile; /* windows nt only */ unsigned visited; /* checks and fork */ @@ -26,15 +45,31 @@ struct Map { }; }; +struct MapLock { + void *edges; + _Atomic(uint64_t) word; +}; + +struct MapSlab { + struct MapSlab *next; + struct Map maps[(MAPS_SIZE - sizeof(struct MapSlab *)) / sizeof(struct Map)]; +}; + struct Maps { + uint128_t rand; struct Tree *maps; - _Atomic(uint64_t) lock; + struct MapLock lock; _Atomic(uintptr_t) freed; + _Atomic(struct MapSlab *) slabs; size_t count; size_t pages; - _Atomic(char *) pick; struct Map stack; struct Map guard; +#ifdef MODE_DBG + struct Map spool[1]; +#else + struct Map spool[20]; +#endif }; struct AddrSize { @@ -44,16 +79,18 @@ struct AddrSize { extern struct Maps __maps; +bool __maps_held(void); void __maps_init(void); -bool __maps_lock(void); +void __maps_lock(void); void __maps_check(void); void __maps_unlock(void); +bool __maps_reentrant(void); void *__maps_randaddr(void); -void *__maps_pickaddr(size_t); void __maps_add(struct Map *); void __maps_free(struct Map *); void __maps_insert(struct Map *); -bool __maps_track(char *, size_t); +int __maps_untrack(char *, size_t); +bool __maps_track(char *, size_t, int, int); struct Map *__maps_alloc(void); struct Map *__maps_floor(const char *); void __maps_stack(char *, int, int, size_t, int, intptr_t); @@ -74,6 +111,13 @@ static inline struct Map *__maps_next(struct Map *map) { return 0; } +static inline struct Map *__maps_prev(struct Map *map) { + struct Tree *node; + if ((node = tree_prev(&map->tree))) + return MAP_TREE_CONTAINER(node); + return 0; +} + static inline struct Map *__maps_first(void) { struct Tree *node; if ((node = tree_first(__maps.maps))) @@ -81,5 +125,16 @@ static inline struct Map *__maps_first(void) { return 0; } +static inline struct Map *__maps_last(void) { + struct Tree *node; + if ((node = tree_last(__maps.maps))) + return MAP_TREE_CONTAINER(node); + return 0; +} + +static inline bool __maps_isalloc(struct Map *map) { + return map->hand != MAPS_SUBREGION; +} + COSMOPOLITAN_C_END_ #endif /* COSMOPOLITAN_MAPS_H_ */ diff --git a/libc/intrin/memchr.c b/libc/intrin/memchr.c index 6680c5292..fbe1ad409 100644 --- a/libc/intrin/memchr.c +++ b/libc/intrin/memchr.c @@ -19,10 +19,10 @@ #include "libc/dce.h" #include "libc/nexgen32e/x86feature.h" #include "libc/str/str.h" +#include "third_party/aarch64/arm_neon.internal.h" +#include "third_party/intel/immintrin.internal.h" #ifndef __aarch64__ -typedef char xmm_t __attribute__((__vector_size__(16), __aligned__(1))); - static inline const unsigned char *memchr_pure(const unsigned char *s, unsigned char c, size_t n) { size_t i; @@ -35,22 +35,27 @@ static inline const unsigned char *memchr_pure(const unsigned char *s, } #if defined(__x86_64__) && !defined(__chibicc__) -static __vex const unsigned char *memchr_sse(const unsigned char *s, - unsigned char c, size_t n) { - size_t i; - unsigned m; - xmm_t v, t = {c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c}; - for (; n >= 16; n -= 16, s += 16) { - v = *(const xmm_t *)s; - m = __builtin_ia32_pmovmskb128(v == t); - if (m) { - m = __builtin_ctzll(m); - return s + m; - } +static const char *memchr_sse(const char *s, char c, size_t n) { + const char *e = s + n; + __m128i t = _mm_set1_epi8(c); + unsigned m, k = (uintptr_t)s & 15; + m = _mm_movemask_epi8( + _mm_cmpeq_epi8(_mm_load_si128((const __m128i *)((uintptr_t)s & -16)), t)); + m >>= k; + if (m) { + s += __builtin_ctz(m); + if (s < e) + return s; + return 0; } - for (i = 0; i < n; ++i) { - if (s[i] == c) { - return s + i; + for (s += 16 - k; s < e; s += 16) { + m = _mm_movemask_epi8( + _mm_cmpeq_epi8(_mm_load_si128((const __m128i *)s), t)); + if (m) { + s += __builtin_ctz(m); + if (s < e) + return s; + return 0; } } return 0; diff --git a/libc/intrin/mmap.c b/libc/intrin/mmap.c index 64a4c49c6..de3b5571a 100644 --- a/libc/intrin/mmap.c +++ b/libc/intrin/mmap.c @@ -18,7 +18,9 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/calls/internal.h" +#include "libc/calls/state.internal.h" #include "libc/calls/syscall-sysv.internal.h" +#include "libc/calls/syscall_support-nt.internal.h" #include "libc/dce.h" #include "libc/errno.h" #include "libc/intrin/atomic.h" @@ -30,25 +32,35 @@ #include "libc/intrin/strace.h" #include "libc/intrin/tree.h" #include "libc/intrin/weaken.h" +#include "libc/limits.h" +#include "libc/macros.h" +#include "libc/nt/enum/filemapflags.h" +#include "libc/nt/enum/memflags.h" +#include "libc/nt/enum/pageflags.h" +#include "libc/nt/errors.h" #include "libc/nt/memory.h" #include "libc/nt/runtime.h" #include "libc/runtime/runtime.h" +#include "libc/runtime/syslib.internal.h" #include "libc/runtime/zipos.internal.h" -#include "libc/stdio/rand.h" +#include "libc/stdckdint.h" #include "libc/stdio/sysparam.h" #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/mremap.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/prot.h" #include "libc/sysv/errfuns.h" +#include "libc/thread/lock.h" +#include "libc/thread/thread.h" +#include "libc/thread/tls.h" -#define MMDEBUG IsModeDbg() -#define MAX_SIZE 0x0ff800000000ul -#define MAX_TRIES 50 +#define MMDEBUG 0 +#define MAX_SIZE 0x0ff800000000ul #define MAP_FIXED_NOREPLACE_linux 0x100000 -#define PGUP(x) (((x) + pagesz - 1) & -pagesz) +#define PGUP(x) (((x) + __pagesize - 1) & -__pagesize) +#define GRUP(x) (((x) + __gransize - 1) & -__gransize) #define MASQUE 0x00fffffffffffff8 #define PTR(x) ((uintptr_t)(x) & MASQUE) @@ -73,6 +85,11 @@ } while (0) #endif +struct DirectMap { + void *addr; + int64_t hand; +}; + int __maps_compare(const struct Tree *ra, const struct Tree *rb) { const struct Map *a = (const struct Map *)MAP_TREE_CONTAINER(ra); const struct Map *b = (const struct Map *)MAP_TREE_CONTAINER(rb); @@ -86,20 +103,50 @@ privileged optimizespeed struct Map *__maps_floor(const char *addr) { return 0; } -static bool __maps_overlaps(const char *addr, size_t size, int pagesz) { - struct Map *map, *floor = __maps_floor(addr); - for (map = floor; map && map->addr <= addr + size; map = __maps_next(map)) +static bool __maps_overlaps(const char *addr, size_t size) { + struct Map *map; + ASSERT(__maps_held()); + if (!(map = __maps_floor(addr))) + map = __maps_first(); + for (; map && map->addr <= addr + size; map = __maps_next(map)) if (MAX(addr, map->addr) < MIN(addr + PGUP(size), map->addr + PGUP(map->size))) return true; return false; } +// returns true if all fragments of all allocations which overlap +// [addr,addr+size) are completely contained by [addr,addr+size). +textwindows static bool __maps_envelops(const char *addr, size_t size) { + struct Map *map; + size = PGUP(size); + ASSERT(__maps_held()); + if (!(map = __maps_floor(addr))) + map = __maps_first(); + while (map && map->addr <= addr + size) { + if (MAX(addr, map->addr) < MIN(addr + size, map->addr + map->size)) { + if (!__maps_isalloc(map)) + return false; // didn't include first fragment of alloc + if (addr > map->addr) + return false; // excluded leading pages of first fragment + struct Map *next; // set map to last fragment in allocation + for (; (next = __maps_next(map)) && !__maps_isalloc(next); map = next) + ASSERT(map->addr + map->size == next->addr); // contiguous + if (addr + size < map->addr + PGUP(map->size)) + return false; // excluded trailing pages of allocation + map = next; + } else { + map = __maps_next(map); + } + } + return true; +} + void __maps_check(void) { #if MMDEBUG + ASSERT(__maps_held()); size_t maps = 0; size_t pages = 0; - int pagesz = __pagesize; static unsigned mono; unsigned id = ++mono; for (struct Map *map = __maps_first(); map; map = __maps_next(map)) { @@ -107,7 +154,7 @@ void __maps_check(void) { ASSERT(map->visited != id); ASSERT(map->size); map->visited = id; - pages += (map->size + pagesz - 1) / pagesz; + pages += (map->size + __pagesize - 1) / __pagesize; maps += 1; struct Map *next; if ((next = __maps_next(map))) { @@ -121,110 +168,112 @@ void __maps_check(void) { #endif } -static int __muntrack(char *addr, size_t size, int pagesz, - struct Map **deleted) { +#if MMDEBUG +static void __maps_ok(void) { + ASSERT(!__maps_reentrant()); + __maps_lock(); + __maps_check(); + __maps_unlock(); +} +__attribute__((__constructor__)) static void __maps_ctor(void) { + atexit(__maps_ok); + __maps_ok(); +} +__attribute__((__destructor__)) static void __maps_dtor(void) { + __maps_ok(); +} +#endif + +static int __muntrack(char *addr, size_t size, struct Map **deleted, + struct Map **untracked, struct Map temp[2]) { int rc = 0; + size_t ti = 0; struct Map *map; struct Map *next; - struct Map *floor; -StartOver: - floor = __maps_floor(addr); - for (map = floor; map && map->addr <= addr + size; map = next) { + size = PGUP(size); + ASSERT(__maps_held()); + if (!(map = __maps_floor(addr))) + map = __maps_first(); + for (; map && map->addr <= addr + size; map = next) { next = __maps_next(map); char *map_addr = map->addr; size_t map_size = map->size; - if (!(MAX(addr, map_addr) < - MIN(addr + PGUP(size), map_addr + PGUP(map_size)))) + if (MAX(addr, map_addr) >= MIN(addr + size, map_addr + PGUP(map_size))) continue; - if (addr <= map_addr && addr + PGUP(size) >= map_addr + PGUP(map_size)) { + if (addr <= map_addr && addr + size >= map_addr + PGUP(map_size)) { + if (map->hand == MAPS_RESERVATION) + continue; // remove mapping completely tree_remove(&__maps.maps, &map->tree); map->freed = *deleted; *deleted = map; - __maps.pages -= (map_size + pagesz - 1) / pagesz; + __maps.pages -= (map_size + __pagesize - 1) / __pagesize; __maps.count -= 1; __maps_check(); - } else if (IsWindows()) { - STRACE("you can't carve up memory maps on windows ;_;"); - rc = einval(); } else if (addr <= map_addr) { // shave off lefthand side of mapping - ASSERT(addr + PGUP(size) < map_addr + PGUP(map_size)); - size_t left = addr + PGUP(size) - map_addr; + ASSERT(addr + size < map_addr + PGUP(map_size)); + size_t left = addr + size - map_addr; size_t right = map_size - left; ASSERT(right > 0); ASSERT(left > 0); - struct Map *leftmap; - if ((leftmap = __maps_alloc())) { - if (leftmap == MAPS_RETRY) - goto StartOver; - map->addr += left; - map->size = right; - if (!(map->flags & MAP_ANONYMOUS)) - map->off += left; - __maps.pages -= (left + pagesz - 1) / pagesz; - leftmap->addr = map_addr; - leftmap->size = left; - leftmap->freed = *deleted; - *deleted = leftmap; - __maps_check(); - } else { - rc = -1; + map->addr += left; + map->size = right; + if (!(map->flags & MAP_ANONYMOUS)) + map->off += left; + __maps.pages -= (left + __pagesize - 1) / __pagesize; + if (untracked) { + ASSERT(ti < 2); + temp[ti].addr = map_addr; + temp[ti].size = left; + temp[ti].freed = *untracked; + *untracked = temp; + ++ti; } - } else if (addr + PGUP(size) >= map_addr + PGUP(map_size)) { + __maps_check(); + } else if (addr + size >= map_addr + PGUP(map_size)) { // shave off righthand side of mapping size_t left = addr - map_addr; size_t right = map_addr + map_size - addr; - struct Map *rightmap; - if ((rightmap = __maps_alloc())) { - if (rightmap == MAPS_RETRY) - goto StartOver; - map->size = left; - __maps.pages -= (right + pagesz - 1) / pagesz; - rightmap->addr = addr; - rightmap->size = right; - rightmap->freed = *deleted; - *deleted = rightmap; - __maps_check(); - } else { - rc = -1; + map->size = left; + __maps.pages -= (right + __pagesize - 1) / __pagesize; + if (untracked) { + ASSERT(ti < 2); + temp[ti].addr = addr; + temp[ti].size = right; + temp[ti].freed = *untracked; + *untracked = temp; + ++ti; } + __maps_check(); } else { // punch hole in mapping size_t left = addr - map_addr; - size_t middle = PGUP(size); + size_t middle = size; size_t right = map_size - middle - left; struct Map *leftmap; if ((leftmap = __maps_alloc())) { - if (leftmap == MAPS_RETRY) - goto StartOver; - struct Map *middlemap; - if ((middlemap = __maps_alloc())) { - if (middlemap == MAPS_RETRY) { - __maps_free(leftmap); - goto StartOver; - } - leftmap->addr = map_addr; - leftmap->size = left; - leftmap->off = map->off; - leftmap->prot = map->prot; - leftmap->flags = map->flags; - map->addr += left + middle; - map->size = right; - if (!(map->flags & MAP_ANONYMOUS)) - map->off += left + middle; - tree_insert(&__maps.maps, &leftmap->tree, __maps_compare); - __maps.pages -= (middle + pagesz - 1) / pagesz; - __maps.count += 1; - middlemap->addr = addr; - middlemap->size = size; - middlemap->freed = *deleted; - *deleted = middlemap; - __maps_check(); - } else { - __maps_free(leftmap); - rc = -1; + leftmap->addr = map_addr; + leftmap->size = left; + leftmap->off = map->off; + leftmap->prot = map->prot; + leftmap->flags = map->flags; + map->addr += left + middle; + map->size = right; + if (!(map->flags & MAP_ANONYMOUS)) + map->off += left + middle; + tree_insert(&__maps.maps, &leftmap->tree, __maps_compare); + __maps.pages -= (middle + __pagesize - 1) / __pagesize; + __maps.count += 1; + if (untracked) { + ASSERT(ti < 2); + temp[ti].addr = addr; + temp[ti].size = size; + temp[ti].freed = *untracked; + *untracked = temp; + ++ti; } + __maps_check(); } else { rc = -1; } @@ -238,13 +287,13 @@ void __maps_free(struct Map *map) { ASSERT(!TAG(map)); map->size = 0; map->addr = MAP_FAILED; + map->hand = kNtInvalidHandleValue; for (tip = atomic_load_explicit(&__maps.freed, memory_order_relaxed);;) { map->freed = (struct Map *)PTR(tip); if (atomic_compare_exchange_weak_explicit( &__maps.freed, &tip, ABA(map, TAG(tip) + 1), memory_order_release, memory_order_relaxed)) break; - pthread_pause_np(); } } @@ -256,147 +305,333 @@ static void __maps_free_all(struct Map *list) { } } -void __maps_insert(struct Map *map) { - map->flags &= MAP_TYPE | MAP_ANONYMOUS | MAP_NOFORK; +static void __maps_insert_all(struct Map *list) { + struct Map *next; + for (struct Map *map = list; map; map = next) { + next = map->freed; + __maps_insert(map); + } +} - // coalesce adjacent mappings - if (!IsWindows() && (map->flags & MAP_ANONYMOUS)) { - int prot = map->prot & ~(MAP_FIXED | MAP_FIXED_NOREPLACE); - int flags = map->flags; - bool coalesced = false; - struct Map *floor, *other, *last = 0; - for (other = floor = __maps_floor(map->addr); - other && other->addr <= map->addr + map->size; - last = other, other = __maps_next(other)) { - if (prot == other->prot && flags == other->flags) { - if (!coalesced) { - if (map->addr == other->addr + other->size) { - __maps.pages += (map->size + __pagesize - 1) / __pagesize; - other->size += map->size; - __maps_free(map); - __maps_check(); - coalesced = true; - } else if (map->addr + map->size == other->addr) { - __maps.pages += (map->size + __pagesize - 1) / __pagesize; - other->addr -= map->size; - other->size += map->size; - __maps_free(map); - __maps_check(); - coalesced = true; - } - } - if (last && other->addr == last->addr + last->size) { - other->addr -= last->size; - other->size += last->size; - tree_remove(&__maps.maps, &last->tree); - __maps.count -= 1; - __maps_free(last); - __maps_check(); - } +static int __maps_destroy_all(struct Map *list) { + int rc = 0; + for (struct Map *map = list; map; map = map->freed) { + if (!IsWindows()) { + if (sys_munmap(map->addr, map->size)) + rc = -1; + } else { + switch (map->hand) { + case MAPS_SUBREGION: + case MAPS_RESERVATION: + break; + case MAPS_VIRTUAL: + if (!VirtualFree(map->addr, 0, kNtMemRelease)) + rc = __winerr(); + break; + default: + ASSERT(map->hand > 0); + if (!UnmapViewOfFile(map->addr)) + rc = -1; + if (!CloseHandle(map->hand)) + rc = -1; + break; } } - if (coalesced) - return; + } + return rc; +} + +static int __maps_funge_flags(int flags) { + flags &= ~MAP_FIXED; + flags &= ~MAP_FIXED_NOREPLACE; + if ((flags & MAP_TYPE) == MAP_SHARED_VALIDATE) { + flags &= ~MAP_TYPE; + flags |= MAP_SHARED; + } + return flags; +} + +static bool __maps_fungible(const struct Map *map) { + // anonymous memory is fungible on unix, so we may coalesce such + // mappings in the rbtree to have fewer objects. on windows even + // anonymous memory has unique win32 handles we need to preserve + return !IsWindows() && (map->flags & MAP_ANONYMOUS); +} + +static bool __maps_adjacent(const struct Map *x, const struct Map *y) { + char *a = x->addr + PGUP(x->size); + char *b = y->addr; + ASSERT(a <= b); + return a == b; +} + +static bool __maps_mergeable(const struct Map *x, const struct Map *y) { + if (!__maps_adjacent(x, y)) + return false; + if (!__maps_fungible(x)) + return false; + if (!__maps_fungible(y)) + return false; + if (x->prot != y->prot) + return false; + if (__maps_funge_flags(x->flags) != __maps_funge_flags(y->flags)) + return false; + return true; +} + +void __maps_insert(struct Map *map) { + struct Map *left, *right; + ASSERT(map->size); + ASSERT(__maps_held()); + ASSERT(!__maps_overlaps(map->addr, map->size)); + __maps.pages += (map->size + __pagesize - 1) / __pagesize; + + // find adjacent mappings + if ((left = __maps_floor(map->addr))) { + right = __maps_next(left); + } else { + right = __maps_first(); } - // otherwise insert new mapping - __maps.pages += (map->size + __pagesize - 1) / __pagesize; - __maps_add(map); + // avoid insert by making mapping on left bigger + if (left) + if (__maps_mergeable(left, map)) { + left->size = PGUP(left->size); + left->size += map->size; + __maps_free(map); + map = 0; + } + + // avoid insert by making mapping on right bigger + if (map && right) + if (__maps_mergeable(map, right)) { + map->size = PGUP(map->size); + right->addr -= map->size; + right->size += map->size; + __maps_free(map); + map = 0; + } + + // check if we filled a hole + if (!map && left && right) + if (__maps_mergeable(left, right)) { + left->size = PGUP(left->size); + left->size += right->size; + tree_remove(&__maps.maps, &right->tree); + __maps_free(right); + __maps.count -= 1; + } + + // otherwise just insert + if (map) + __maps_add(map); + + // sanity check __maps_check(); } -static void __maps_track_insert(struct Map *map, char *addr, size_t size, - uintptr_t map_handle) { +// adds interval to rbtree +bool __maps_track(char *addr, size_t size, int prot, int flags) { + struct Map *map; + if (!(map = __maps_alloc())) + return false; map->addr = addr; map->size = size; - map->prot = PROT_READ | PROT_WRITE; - map->flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NOFORK; - map->hand = map_handle; + map->prot = prot; + map->flags = flags; + map->hand = MAPS_VIRTUAL; __maps_lock(); __maps_insert(map); __maps_unlock(); + return true; } -bool __maps_track(char *addr, size_t size) { - struct Map *map; - do { - if (!(map = __maps_alloc())) - return false; - } while (map == MAPS_RETRY); - __maps_track_insert(map, addr, size, -1); - return true; +// removes interval from rbtree (no sys_munmap) +int __maps_untrack(char *addr, size_t size) { + struct Map *deleted = 0; + __maps_lock(); + int rc = __muntrack(addr, size, &deleted, 0, 0); + __maps_unlock(); + __maps_free_all(deleted); + return rc; +} + +textwindows dontinline static struct DirectMap sys_mmap_nt( + void *addr, size_t size, int prot, int flags, int fd, int64_t off) { + struct DirectMap dm; + + // it's 5x faster + if ((flags & MAP_ANONYMOUS) && (flags & MAP_TYPE) != MAP_SHARED) { + if (!(dm.addr = VirtualAlloc(addr, size, kNtMemReserve | kNtMemCommit, + __prot2nt(prot, false)))) { + dm.addr = MAP_FAILED; + } + dm.hand = MAPS_VIRTUAL; + return dm; + } + + int64_t file_handle; + if (flags & MAP_ANONYMOUS) { + file_handle = kNtInvalidHandleValue; + } else { + file_handle = g_fds.p[fd].handle; + } + + // mark map handle as inheritable if fork might need it + const struct NtSecurityAttributes *mapsec; + if ((flags & MAP_TYPE) == MAP_SHARED) { + mapsec = &kNtIsInheritable; + } else { + mapsec = 0; + } + + // nt will whine under many circumstances if we change the execute bit + // later using mprotect(). the workaround is to always request execute + // and then virtualprotect() it away until we actually need it. please + // note that open-nt.c always requests an kNtGenericExecute accessmask + int iscow = 0; + int page_flags; + int file_flags; + if (file_handle != -1) { + if ((flags & MAP_TYPE) != MAP_SHARED) { + // windows has cow pages but they can't propagate across fork() + // that means we only get copy-on-write for the root process :( + page_flags = kNtPageExecuteWritecopy; + file_flags = kNtFileMapCopy | kNtFileMapExecute; + iscow = 1; + } else { + if ((g_fds.p[fd].flags & O_ACCMODE) == O_RDONLY) { + page_flags = kNtPageExecuteRead; + file_flags = kNtFileMapRead | kNtFileMapExecute; + } else { + page_flags = kNtPageExecuteReadwrite; + file_flags = kNtFileMapWrite | kNtFileMapExecute; + } + } + } else { + page_flags = kNtPageExecuteReadwrite; + file_flags = kNtFileMapWrite | kNtFileMapExecute; + } + + int e = errno; +TryAgain: + if ((dm.hand = CreateFileMapping(file_handle, mapsec, page_flags, + (size + off) >> 32, (size + off), 0))) { + if ((dm.addr = MapViewOfFileEx(dm.hand, file_flags, off >> 32, off, size, + addr))) { + uint32_t oldprot; + if (VirtualProtect(dm.addr, size, __prot2nt(prot, iscow), &oldprot)) + return dm; + UnmapViewOfFile(dm.addr); + } + CloseHandle(dm.hand); + } else if (!(prot & PROT_EXEC) && // + (file_flags & kNtFileMapExecute) && // + GetLastError() == kNtErrorAccessDenied) { + // your file needs to have been O_CREAT'd with exec `mode` bits in + // order to be mapped with executable permission. we always try to + // get execute permission if the kernel will give it to us because + // win32 would otherwise forbid mprotect() from elevating later on + file_flags &= ~kNtFileMapExecute; + switch (page_flags) { + case kNtPageExecuteWritecopy: + page_flags = kNtPageWritecopy; + break; + case kNtPageExecuteReadwrite: + page_flags = kNtPageReadwrite; + break; + case kNtPageExecuteRead: + page_flags = kNtPageReadonly; + break; + default: + __builtin_unreachable(); + } + errno = e; + goto TryAgain; + } + + dm.hand = kNtInvalidHandleValue; + dm.addr = (void *)(intptr_t)-1; + return dm; +} + +static struct DirectMap sys_mmap(void *addr, size_t size, int prot, int flags, + int fd, int64_t off) { + struct DirectMap d; + if (IsXnuSilicon()) { + long p = _sysret(__syslib->__mmap(addr, size, prot, flags, fd, off)); + d.hand = kNtInvalidHandleValue; + d.addr = (void *)p; + } else if (IsWindows()) { + d = sys_mmap_nt(addr, size, prot, flags, fd, off); + } else if (IsMetal()) { + d.addr = sys_mmap_metal(addr, size, prot, flags, fd, off); + d.hand = kNtInvalidHandleValue; + } else { + d.addr = __sys_mmap(addr, size, prot, flags, fd, off, off); + d.hand = kNtInvalidHandleValue; + } + return d; } struct Map *__maps_alloc(void) { struct Map *map; uintptr_t tip = atomic_load_explicit(&__maps.freed, memory_order_relaxed); - while ((map = (struct Map *)PTR(tip))) { + while ((map = (struct Map *)PTR(tip))) if (atomic_compare_exchange_weak_explicit( &__maps.freed, &tip, ABA(map->freed, TAG(tip) + 1), memory_order_acquire, memory_order_relaxed)) return map; - pthread_pause_np(); - } - int gransz = __gransize; - struct DirectMap sys = sys_mmap(0, gransz, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + // we're creating sudden surprise memory. the user might be in the + // middle of carefully planning a fixed memory structure. we don't + // want the system allocator to put our surprise memory inside it, + // and we also want to avoid the chances of accidentally unmapping + struct DirectMap sys = + sys_mmap(__maps_randaddr(), MAPS_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (sys.addr == MAP_FAILED) return 0; - map = sys.addr; - __maps_track_insert(map, sys.addr, gransz, sys.maphandle); - for (int i = 1; i < gransz / sizeof(struct Map); ++i) - __maps_free(map + i); - return MAPS_RETRY; + struct MapSlab *slab = sys.addr; + while (!atomic_compare_exchange_weak(&__maps.slabs, &slab->next, slab)) { + } + for (size_t i = 1; i < ARRAYLEN(slab->maps); ++i) + __maps_free(&slab->maps[i]); + return &slab->maps[0]; } static int __munmap(char *addr, size_t size) { // validate arguments - int pagesz = __pagesize; - int gransz = __gransize; - if (((uintptr_t)addr & (gransz - 1)) || // + if (((uintptr_t)addr & (__gransize - 1)) || // !size || (uintptr_t)addr + size < size) return einval(); - // lock the memory manager - // abort on reentry due to signal handler - if (__maps_lock()) { - __maps_unlock(); + // test for signal handler tragedy + if (__maps_reentrant()) return edeadlk(); - } + + // lock the memory manager + __maps_lock(); __maps_check(); - // normalize size - // abort if size doesn't include all pages in granule - size_t pgup_size = (size + pagesz - 1) & -pagesz; - size_t grup_size = (size + gransz - 1) & -gransz; - if (grup_size > pgup_size) - if (__maps_overlaps(addr + pgup_size, grup_size - pgup_size, pagesz)) { + // on windows we can only unmap whole allocations + if (IsWindows()) + if (!__maps_envelops(addr, size)) { __maps_unlock(); - return einval(); + return enotsup(); } // untrack mappings int rc; + struct Map temp[2]; struct Map *deleted = 0; - rc = __muntrack(addr, pgup_size, pagesz, &deleted); + struct Map *untracked = 0; + rc = __muntrack(addr, size, &deleted, &untracked, temp); __maps_unlock(); - // delete mappings - for (struct Map *map = deleted; map; map = map->freed) { - if (!IsWindows()) { - if (sys_munmap(map->addr, map->size)) - rc = -1; - } else if (map->hand != -1) { - ASSERT(!((uintptr_t)map->addr & (gransz - 1))); - if (!UnmapViewOfFile(map->addr)) - rc = -1; - if (!CloseHandle(map->hand)) - rc = -1; - } - } - - // freed mappings + // ask operating system to remove mappings + rc |= __maps_destroy_all(untracked); + rc |= __maps_destroy_all(deleted); __maps_free_all(deleted); return rc; @@ -404,46 +639,76 @@ static int __munmap(char *addr, size_t size) { void *__maps_randaddr(void) { uintptr_t addr; - addr = _rand64(); + __maps_lock(); + addr = (__maps.rand *= 15750249268501108917ull) >> 64; + __maps_unlock(); addr &= 0x3fffffffffff; addr |= 0x004000000000; addr &= -__gransize; return (void *)addr; } -void *__maps_pickaddr(size_t size) { - char *addr; - for (int try = 0; try < MAX_TRIES; ++try) { - addr = atomic_exchange_explicit(&__maps.pick, 0, memory_order_acq_rel); - if (!addr) - addr = __maps_randaddr(); - __maps_lock(); - bool overlaps = __maps_overlaps(addr, size, __pagesize); - __maps_unlock(); - if (!overlaps) { - atomic_store_explicit(&__maps.pick, - addr + ((size + __gransize - 1) & __gransize), - memory_order_release); - return addr; +static void *__maps_pickaddr(size_t size) { + ASSERT(__maps_held()); + char *addr = 0; + struct Map *map, *prev; + size = GRUP(size); + if ((map = __maps_last())) { + // choose address beneath higher mapping + for (; map; map = prev) { + char *min = (char *)(intptr_t)__gransize; + if ((prev = __maps_prev(map))) + min = prev->addr + GRUP(prev->size); + if (map->addr > min && // + map->addr - min >= size) { + addr = map->addr - size; + break; + } } + // append if existing maps are too dense + if (!addr) { + map = __maps_last(); + addr = map->addr + GRUP(map->size); + intptr_t end = (intptr_t)addr; + if (ckd_add(&end, end, size)) + return 0; + } + } else { + // roll the dice if rbtree is empty + addr = __maps_randaddr(); } - return 0; + return addr; } -static void *__mmap_chunk(void *addr, size_t size, int prot, int flags, int fd, - int64_t off, int pagesz, int gransz) { +static void *__mmap_impl(char *addr, size_t size, int prot, int flags, int fd, + int64_t off) { + + // validate file map args + if (flags & MAP_ANONYMOUS) { + // some operating systems will complain unless we do this + fd = -1; + off = 0; + } else { + // validate arguments for file mapping + if (off & (__gransize - 1)) + return (void *)einval(); + if (IsWindows()) { + if (!__isfdkind(fd, kFdFile)) + return (void *)eacces(); + if ((g_fds.p[fd].flags & O_ACCMODE) == O_WRONLY) + return (void *)eacces(); + } + } // allocate Map object struct Map *map; - do { - if (!(map = __maps_alloc())) - return MAP_FAILED; - } while (map == MAPS_RETRY); + if (!(map = __maps_alloc())) + return MAP_FAILED; // polyfill nuances of fixed mappings int sysflags = flags; bool noreplace = false; - bool should_untrack = false; + bool fixedmode = false; if (flags & MAP_FIXED_NOREPLACE) { if (flags & MAP_FIXED) { __maps_free(map); @@ -454,39 +719,107 @@ static void *__mmap_chunk(void *addr, size_t size, int prot, int flags, int fd, noreplace = true; sysflags |= MAP_FIXED_NOREPLACE_linux; } else if (IsFreebsd() || IsNetbsd()) { + // todo: insert a reservation like windows sysflags |= MAP_FIXED; - if (__maps_overlaps(addr, size, pagesz)) { + __maps_lock(); + if (__maps_overlaps(addr, size)) { + __maps_unlock(); __maps_free(map); return (void *)eexist(); } + __maps_unlock(); } else { noreplace = true; } } else if (flags & MAP_FIXED) { - should_untrack = true; + fixedmode = true; } - // remove mapping we blew away - if (IsWindows() && should_untrack) - __munmap(addr, size); - - // obtain mapping from operating system + // loop for memory int olderr = errno; - int tries = MAX_TRIES; struct DirectMap res; -TryAgain: - res = sys_mmap(addr, size, prot, sysflags, fd, off); - if (res.addr == MAP_FAILED) { - if (IsWindows() && errno == EADDRNOTAVAIL) { - if (noreplace) { - errno = EEXIST; - } else if (should_untrack) { - errno = ENOMEM; - } else if (--tries && (addr = __maps_pickaddr(size))) { - errno = olderr; - goto TryAgain; + for (;;) { + + // transactionally find the mark on windows + if (IsWindows()) { + __maps_lock(); + if (!fixedmode) { + // give user desired address if possible + if (addr && __maps_overlaps(addr, size)) { + if (noreplace) { + __maps_unlock(); + __maps_free(map); + return (void *)eexist(); + } + addr = 0; + } + // choose suitable address then claim it in our rbtree + if (!addr && !(addr = __maps_pickaddr(size))) { + __maps_unlock(); + __maps_free(map); + return (void *)enomem(); + } } else { - errno = ENOMEM; + // remove existing mappings and their tracking objects + if (!__maps_envelops(addr, size)) { + __maps_unlock(); + __maps_free(map); + return (void *)enotsup(); + } + struct Map *deleted = 0; + if (__muntrack(addr, size, &deleted, 0, 0)) { + __maps_insert_all(deleted); + __maps_unlock(); + __maps_free(map); + return MAP_FAILED; + } + int rc = __maps_destroy_all(deleted); + __maps_free_all(deleted); + if (rc) { + __maps_unlock(); + __maps_free(map); + return (void *)eperm(); + } + } + // claims intended interval while still holding the lock + map->addr = addr; + map->size = size; + map->prot = 0; + map->flags = 0; + map->hand = MAPS_RESERVATION; + __maps_insert(map); + __maps_unlock(); + } + + // ask operating system for our memory + // notice how we're not holding the lock + res = sys_mmap(addr, size, prot, sysflags, fd, off); + if (res.addr != MAP_FAILED) + break; + + // handle failure + if (IsWindows()) { + // untrack reservation + __maps_lock(); + tree_remove(&__maps.maps, &map->tree); + __maps.pages -= (map->size + __pagesize - 1) / __pagesize; + __maps_unlock(); + if (errno == EADDRNOTAVAIL) { + // we've encountered mystery memory + if (fixedmode) { + // TODO(jart): Use VirtualQuery() to destroy mystery memory. + errno = ENOMEM; + } else if (noreplace) { + // we can't try again with a different address in this case + errno = EEXIST; + } else { + // we shall leak the tracking object since it should at least + // partially cover the mystery mapping. so if we loop forever + // the system should eventually recover and find fresh spaces + errno = olderr; + addr = 0; + continue; + } } } __maps_free(map); @@ -494,79 +827,49 @@ TryAgain: } // polyfill map fixed noreplace - // we assume non-linux gives us addr if it's freed - // that's what linux (e.g. rhel7) did before noreplace if (noreplace && res.addr != addr) { - if (!IsWindows()) { - sys_munmap(res.addr, size); - } else { - UnmapViewOfFile(res.addr); - CloseHandle(res.maphandle); - } + ASSERT(!IsWindows()); + sys_munmap(res.addr, size); __maps_free(map); return (void *)eexist(); } - // untrack mapping we blew away - if (!IsWindows() && should_untrack) { - struct Map *deleted = 0; - __muntrack(res.addr, size, pagesz, &deleted); - __maps_free_all(deleted); - } - - // track map object + // setup map object map->addr = res.addr; map->size = size; map->off = off; map->prot = prot; map->flags = flags; - map->hand = res.maphandle; + map->hand = res.hand; if (IsWindows()) { map->iscow = (flags & MAP_TYPE) != MAP_SHARED && fd != -1; map->readonlyfile = (flags & MAP_TYPE) == MAP_SHARED && fd != -1 && (g_fds.p[fd].flags & O_ACCMODE) == O_RDONLY; } - __maps_lock(); - __maps_insert(map); - __maps_unlock(); - return res.addr; -} - -static void *__mmap_impl(char *addr, size_t size, int prot, int flags, int fd, - int64_t off, int pagesz, int gransz) { - - // validate file map args - if (!(flags & MAP_ANONYMOUS)) { - if (off & (gransz - 1)) - return (void *)einval(); - if (IsWindows()) { - if (!__isfdkind(fd, kFdFile)) - return (void *)eacces(); - if ((g_fds.p[fd].flags & O_ACCMODE) == O_WRONLY) - return (void *)eacces(); - } + // track map object + if (!IsWindows()) { + struct Map *deleted = 0; + __maps_lock(); + if (fixedmode) + if (__muntrack(res.addr, size, &deleted, 0, 0)) + STRACE("memtrack compromised by hole punch oom"); + __maps_insert(map); + __maps_unlock(); + __maps_free_all(deleted); + } else { + atomic_thread_fence(memory_order_release); } - // try to pick our own addresses on windows which are higher up in the - // vaspace. this is important so that conflicts are less likely, after - // forking when resurrecting mappings, because win32 has a strong pref - // with lower memory addresses which may get assigned to who knows wut - if (IsWindows() && !addr) - if (!(addr = __maps_pickaddr(size))) - return (void *)enomem(); - - return __mmap_chunk(addr, size, prot, flags, fd, off, pagesz, gransz); + return res.addr; } static void *__mmap(char *addr, size_t size, int prot, int flags, int fd, int64_t off) { char *res; - int pagesz = __pagesize; - int gransz = __gransize; // validate arguments - if ((uintptr_t)addr & (gransz - 1)) + if ((uintptr_t)addr & (__gransize - 1)) addr = NULL; if (!addr && (flags & (MAP_FIXED | MAP_FIXED_NOREPLACE))) return (void *)eperm(); @@ -576,12 +879,16 @@ static void *__mmap(char *addr, size_t size, int prot, int flags, int fd, return (void *)einval(); if (size > MAX_SIZE) return (void *)enomem(); - if (__maps.count * pagesz + size > __virtualmax) + if (__maps.count * __pagesize + size > __virtualmax) return (void *)enomem(); + // test for signal handler reentry + if (__maps_reentrant()) + return (void *)edeadlk(); + // create memory mappping if (!__isfdkind(fd, kFdZip)) { - res = __mmap_impl(addr, size, prot, flags, fd, off, pagesz, gransz); + res = __mmap_impl(addr, size, prot, flags, fd, off); } else { res = _weaken(__zipos_mmap)( addr, size, prot, flags, @@ -592,40 +899,17 @@ static void *__mmap(char *addr, size_t size, int prot, int flags, int fd, } static void *__mremap_impl(char *old_addr, size_t old_size, size_t new_size, - int flags, char *new_addr, int pagesz, int gransz) { - - // normalize and validate old size - // abort if size doesn't include all pages in granule - size_t pgup_old_size = (old_size + pagesz - 1) & -pagesz; - size_t grup_old_size = (old_size + gransz - 1) & -gransz; - if (grup_old_size > pgup_old_size) - if (__maps_overlaps(old_addr + pgup_old_size, grup_old_size - pgup_old_size, - pagesz)) - return (void *)einval(); - old_size = pgup_old_size; - - // validate new size - // abort if size doesn't include all pages in granule - if (flags & MREMAP_FIXED) { - size_t pgup_new_size = (new_size + pagesz - 1) & -pagesz; - size_t grup_new_size = (new_size + gransz - 1) & -gransz; - if (grup_new_size > pgup_new_size) - if (__maps_overlaps(new_addr + pgup_new_size, - grup_new_size - pgup_new_size, pagesz)) - return (void *)einval(); - } + int flags, char *new_addr) { // allocate object for tracking new mapping struct Map *map; - do { - if (!(map = __maps_alloc())) - return (void *)enomem(); - } while (map == MAPS_RETRY); + if (!(map = __maps_alloc())) + return (void *)enomem(); // check old interval is fully contained within one mapping struct Map *old_map; if (!(old_map = __maps_floor(old_addr)) || - old_addr + old_size > old_map->addr + PGUP(old_map->size) || + old_addr + PGUP(old_size) > old_map->addr + PGUP(old_map->size) || old_addr < old_map->addr) { __maps_free(map); return (void *)efault(); @@ -670,7 +954,7 @@ static void *__mremap_impl(char *old_addr, size_t old_size, size_t new_size, // untrack old mapping struct Map *deleted = 0; - __muntrack(old_addr, old_size, pagesz, &deleted); + __muntrack(old_addr, old_size, &deleted, 0, 0); __maps_free_all(deleted); // track map object @@ -679,6 +963,7 @@ static void *__mremap_impl(char *old_addr, size_t old_size, size_t new_size, map->off = old_off; map->prot = old_prot; map->flags = old_flags; + map->hand = kNtInvalidHandleValue; __maps_insert(map); return res; @@ -687,9 +972,6 @@ static void *__mremap_impl(char *old_addr, size_t old_size, size_t new_size, static void *__mremap(char *old_addr, size_t old_size, size_t new_size, int flags, char *new_addr) { - int pagesz = __pagesize; - int gransz = __gransize; - // kernel support if (!IsLinux() && !IsNetbsd()) return (void *)enosys(); @@ -703,17 +985,16 @@ static void *__mremap(char *old_addr, size_t old_size, size_t new_size, // we support these flags if (flags & ~(MREMAP_MAYMOVE | MREMAP_FIXED)) return (void *)einval(); - if (IsNetbsd() && !(flags & MREMAP_MAYMOVE) && - ((new_size + pagesz - 1) & -pagesz) > old_size) + if (IsNetbsd() && !(flags & MREMAP_MAYMOVE) && PGUP(new_size) > old_size) return (void *)enotsup(); if ((flags & MREMAP_FIXED) && !(flags & MREMAP_MAYMOVE)) return (void *)einval(); // addresses must be granularity aligned - if ((uintptr_t)old_addr & (gransz - 1)) + if ((uintptr_t)old_addr & (__gransize - 1)) return (void *)einval(); if (flags & MREMAP_FIXED) - if ((uintptr_t)new_addr & (gransz - 1)) + if ((uintptr_t)new_addr & (__gransize - 1)) return (void *)einval(); // sizes must not be zero @@ -743,20 +1024,19 @@ static void *__mremap(char *old_addr, size_t old_size, size_t new_size, // memory increase must not exceed RLIMIT_AS if (PGUP(new_size) > old_size) - if (__maps.count * pagesz - old_size + PGUP(new_size) > __virtualmax) + if (__maps.count * __pagesize - old_size + PGUP(new_size) > __virtualmax) return (void *)enomem(); - // lock the memory manager - // abort on reentry due to signal handler - if (__maps_lock()) { - __maps_unlock(); + // test for signal handler reentry + if (__maps_reentrant()) return (void *)edeadlk(); - } + + // lock the memory manager + __maps_lock(); __maps_check(); // perform operation - char *res = __mremap_impl(old_addr, old_size, new_size, flags, new_addr, - pagesz, gransz); + char *res = __mremap_impl(old_addr, old_size, new_size, flags, new_addr); // return result __maps_unlock(); @@ -833,6 +1113,24 @@ static void *__mremap(char *old_addr, size_t old_size, size_t new_size, * The `MAP_CONCEAL` flag may be passed to prevent a memory mapping from * appearing in core dumps. This is currently supported on BSD OSes, and * is ignored on everything else. + * + * POSIX does not require mmap() to be asynchronous signal safe. But you + * should be able to call this from a signal handler safely, if you know + * that your signal will never interrupt the cosmopolitan memory manager + * and the only way you can ensure that, is by blocking signals whenever + * you call mmap(), munmap(), mprotect(), etc. + * + * @raise ENOMEM if `RUSAGE_AS` or similar limits are exceeded + * @raise EEXIST if `flags` has `MAP_FIXED_NOREPLACE` and `addr` is used + * @raise ENOTSUP if interval overlapped without enveloping win32 alloc + * @raise EPERM if `addr` is null and `flags` has `MAP_FIXED` + * @raise EINVAL if `addr` isn't granularity aligned with `MAP_FIXED` + * @raise EINVAL if `size` is zero + * @raise EINVAL if `flags` or `prot` hold invalid values + * @raise EACCESS if `fd` isn't a regular file + * @raise EACCESS if `fd` was opened in write-only mode + * @raise EACCESS if `off` isn't getgransize() aligned + * @raise EDEADLK if called from signal handler interrupting mmap() */ void *mmap(void *addr, size_t size, int prot, int flags, int fd, int64_t off) { void *res = __mmap(addr, size, prot, flags, fd, off); @@ -878,6 +1176,10 @@ void *mremap(void *old_addr, size_t old_size, size_t new_size, int flags, ...) { * The `size` parameter is implicitly rounded up to the page size. * * @return 0 on success, or -1 w/ errno. + * @raise ENOMEM if OOM happened when punching hole in existing mapping + * @raise ENOTSUP if interval overlapped without enveloping win32 alloc + * @raise EDEADLK if called from signal handler interrupting mmap() + * @raise EINVAL if `addr` isn't granularity aligned */ int munmap(void *addr, size_t size) { int rc = __munmap(addr, size); diff --git a/libc/intrin/mprotect.c b/libc/intrin/mprotect.c index 784906acc..393bc641c 100644 --- a/libc/intrin/mprotect.c +++ b/libc/intrin/mprotect.c @@ -22,7 +22,6 @@ #include "libc/intrin/describeflags.h" #include "libc/intrin/directmap.h" #include "libc/intrin/dll.h" -#include "libc/intrin/kprintf.h" #include "libc/intrin/maps.h" #include "libc/intrin/strace.h" #include "libc/intrin/tree.h" @@ -67,17 +66,18 @@ int __mprotect(char *addr, size_t size, int prot) { // normalize size size = (size + pagesz - 1) & -pagesz; + // test for signal handler reentry + if (__maps_reentrant()) + return edeadlk(); + // change mappings int rc = 0; bool found = false; - if (__maps_lock()) { - __maps_unlock(); - return edeadlk(); - } - struct Map *map, *floor; -StartOver: - floor = __maps_floor(addr); - for (map = floor; map && map->addr <= addr + size; map = __maps_next(map)) { + __maps_lock(); + struct Map *map; + if (!(map = __maps_floor(addr))) + map = __maps_first(); + for (; map && map->addr <= addr + size; map = __maps_next(map)) { char *map_addr = map->addr; size_t map_size = map->size; char *beg = MAX(addr, map_addr); @@ -86,7 +86,7 @@ StartOver: continue; found = true; if (addr <= map_addr && addr + size >= map_addr + PGUP(map_size)) { - // change protection of entire mapping + // change protection status of pages if (!__mprotect_chunk(map_addr, map_size, prot, map->iscow)) { map->prot = prot; } else { @@ -98,8 +98,6 @@ StartOver: size_t right = map_size - left; struct Map *leftmap; if ((leftmap = __maps_alloc())) { - if (leftmap == MAPS_RETRY) - goto StartOver; if (!__mprotect_chunk(map_addr, left, prot, false)) { leftmap->addr = map_addr; leftmap->size = left; @@ -111,7 +109,7 @@ StartOver: leftmap->hand = map->hand; map->addr += left; map->size = right; - map->hand = -1; + map->hand = MAPS_SUBREGION; if (!(map->flags & MAP_ANONYMOUS)) map->off += left; tree_insert(&__maps.maps, &leftmap->tree, __maps_compare); @@ -130,8 +128,6 @@ StartOver: size_t right = map_addr + map_size - addr; struct Map *leftmap; if ((leftmap = __maps_alloc())) { - if (leftmap == MAPS_RETRY) - goto StartOver; if (!__mprotect_chunk(map_addr + left, right, prot, false)) { leftmap->addr = map_addr; leftmap->size = left; @@ -144,7 +140,7 @@ StartOver: map->addr += left; map->size = right; map->prot = prot; - map->hand = -1; + map->hand = MAPS_SUBREGION; if (!(map->flags & MAP_ANONYMOUS)) map->off += left; tree_insert(&__maps.maps, &leftmap->tree, __maps_compare); @@ -164,14 +160,8 @@ StartOver: size_t right = map_size - middle - left; struct Map *leftmap; if ((leftmap = __maps_alloc())) { - if (leftmap == MAPS_RETRY) - goto StartOver; struct Map *midlmap; if ((midlmap = __maps_alloc())) { - if (midlmap == MAPS_RETRY) { - __maps_free(leftmap); - goto StartOver; - } if (!__mprotect_chunk(map_addr + left, middle, prot, false)) { leftmap->addr = map_addr; leftmap->size = left; @@ -186,10 +176,10 @@ StartOver: midlmap->off = (map->flags & MAP_ANONYMOUS) ? 0 : map->off + left; midlmap->prot = prot; midlmap->flags = map->flags; - midlmap->hand = -1; + midlmap->hand = MAPS_SUBREGION; map->addr += left + middle; map->size = right; - map->hand = -1; + map->hand = MAPS_SUBREGION; if (!(map->flags & MAP_ANONYMOUS)) map->off += left + middle; tree_insert(&__maps.maps, &leftmap->tree, __maps_compare); @@ -222,11 +212,20 @@ StartOver: /** * Modifies restrictions on virtual memory address range. * - * @param addr needs to be 4kb aligned - * @param prot can have PROT_{NONE,READ,WRITE,EXEC} + * POSIX doesn't require mprotect() to be async signal safe. However you + * should be able to call this from a signal handler safely, if you know + * that your signal will never interrupt the cosmopolitan memory manager + * and the only way you can ensure that, is by blocking signals whenever + * you call mmap(), munmap(), mprotect(), etc. + * + * @param addr needs to be page size aligned + * @param size is rounded up to the page size + * @param prot can be PROT_NONE or a combination of PROT_READ, + * PROT_WRITE, and PROT_EXEC * @return 0 on success, or -1 w/ errno + * @raise EINVAL if `size` is zero * @raise ENOMEM on tracking memory oom - * @see mmap() + * @raise EDEADLK if called from signal handler interrupting mmap() */ int mprotect(void *addr, size_t size, int prot) { int rc; diff --git a/libc/intrin/msync-nt.c b/libc/intrin/msync-nt.c index 73f6ed95a..4e737678b 100644 --- a/libc/intrin/msync-nt.c +++ b/libc/intrin/msync-nt.c @@ -19,34 +19,34 @@ #include "libc/calls/syscall-nt.internal.h" #include "libc/intrin/maps.h" #include "libc/nt/memory.h" -#include "libc/nt/runtime.h" #include "libc/runtime/runtime.h" #include "libc/stdio/sysparam.h" -#include "libc/sysv/consts/auxv.h" +#include "libc/sysv/consts/map.h" #include "libc/sysv/errfuns.h" textwindows int sys_msync_nt(char *addr, size_t size, int flags) { + size = (size + __pagesize - 1) & -__pagesize; - int pagesz = __pagesize; - size = (size + pagesz - 1) & -pagesz; - - if ((uintptr_t)addr & (pagesz - 1)) + if ((uintptr_t)addr & (__pagesize - 1)) return einval(); + if (__maps_reentrant()) + return edeadlk(); int rc = 0; - if (__maps_lock()) { - rc = edeadlk(); - } else { - struct Map *map, *floor; - floor = __maps_floor(addr); - for (map = floor; map && map->addr <= addr + size; map = __maps_next(map)) { - char *beg = MAX(addr, map->addr); - char *end = MIN(addr + size, map->addr + map->size); - if (beg < end) - if (!FlushViewOfFile(beg, end - beg)) - rc = -1; - // TODO(jart): FlushFileBuffers too on g_fds handle if MS_SYNC? - } + __maps_lock(); + struct Map *map; + if (!(map = __maps_floor(addr))) + map = __maps_first(); + for (; map && map->addr <= addr + size; map = __maps_next(map)) { + if (map->flags & MAP_ANONYMOUS) + continue; // msync() is about coherency between file and memory + char *beg = MAX(addr, map->addr); + char *end = MIN(addr + size, map->addr + map->size); + if (beg >= end) + continue; // didn't overlap mapping + if (!FlushViewOfFile(beg, end - beg)) + rc = -1; + // TODO(jart): FlushFileBuffers too on g_fds handle if MS_SYNC? } __maps_unlock(); diff --git a/libc/intrin/msync.c b/libc/intrin/msync.c index 3d241c7a2..e9be44863 100644 --- a/libc/intrin/msync.c +++ b/libc/intrin/msync.c @@ -38,6 +38,7 @@ * @param flags needs MS_ASYNC or MS_SYNC and can have MS_INVALIDATE * @return 0 on success or -1 w/ errno * @raise ECANCELED if thread was cancelled in masked mode + * @raise EDEADLK if called from signal handler interrupting mmap() * @raise EINTR if we needed to block and a signal was delivered instead * @raise EINVAL if `MS_SYNC` and `MS_ASYNC` were both specified * @raise EINVAL if unknown `flags` were passed @@ -67,23 +68,19 @@ int msync(void *addr, size_t size, int flags) { } else { sysflags = MS_ASYNC; } - if (flags & MS_INVALIDATE) { + if (flags & MS_INVALIDATE) sysflags |= MS_INVALIDATE; - } // FreeBSD's manual says "The flags argument was both MS_ASYNC and // MS_INVALIDATE. Only one of these flags is allowed." which makes // following the POSIX recommendation somewhat difficult. - if (IsFreebsd()) { - if (sysflags == (MS_ASYNC | MS_INVALIDATE)) { + if (IsFreebsd()) + if (sysflags == (MS_ASYNC | MS_INVALIDATE)) sysflags = MS_INVALIDATE; - } - } // FreeBSD specifies MS_SYNC as 0 so we shift the Cosmo constants - if (IsFreebsd()) { + if (IsFreebsd()) sysflags >>= 1; - } BEGIN_CANCELATION_POINT; if (!IsWindows()) { diff --git a/libc/intrin/munmap-sysv.c b/libc/intrin/munmap-sysv.c index 0f00ddc5c..3d4b0c6ae 100644 --- a/libc/intrin/munmap-sysv.c +++ b/libc/intrin/munmap-sysv.c @@ -41,8 +41,6 @@ int sys_munmap(void *p, size_t n) { } else { rc = __sys_munmap(p, n); } - if (!rc) - __virtualsize -= n; KERNTRACE("sys_munmap(%p, %'zu) → %d", p, n, rc); return rc; } diff --git a/libc/intrin/ntcontext2linux.c b/libc/intrin/ntcontext2linux.c deleted file mode 100644 index bf9d3df15..000000000 --- a/libc/intrin/ntcontext2linux.c +++ /dev/null @@ -1,82 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ 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 │ -│ 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/ucontext.h" -#include "libc/log/libfatal.internal.h" -#include "libc/nt/struct/context.h" -#include "libc/str/str.h" -#ifdef __x86_64__ - -textwindows void _ntcontext2linux(ucontext_t *ctx, const struct NtContext *cr) { - if (!cr) - return; - ctx->uc_mcontext.eflags = cr->EFlags; - ctx->uc_mcontext.rax = cr->Rax; - ctx->uc_mcontext.rbx = cr->Rbx; - ctx->uc_mcontext.rcx = cr->Rcx; - ctx->uc_mcontext.rdx = cr->Rdx; - ctx->uc_mcontext.rdi = cr->Rdi; - ctx->uc_mcontext.rsi = cr->Rsi; - ctx->uc_mcontext.rbp = cr->Rbp; - ctx->uc_mcontext.rsp = cr->Rsp; - ctx->uc_mcontext.rip = cr->Rip; - ctx->uc_mcontext.r8 = cr->R8; - ctx->uc_mcontext.r9 = cr->R9; - ctx->uc_mcontext.r10 = cr->R10; - ctx->uc_mcontext.r11 = cr->R11; - ctx->uc_mcontext.r12 = cr->R12; - ctx->uc_mcontext.r13 = cr->R13; - ctx->uc_mcontext.r14 = cr->R14; - ctx->uc_mcontext.r15 = cr->R15; - ctx->uc_mcontext.cs = cr->SegCs; - ctx->uc_mcontext.gs = cr->SegGs; - ctx->uc_mcontext.fs = cr->SegFs; - ctx->uc_mcontext.fpregs = &ctx->__fpustate; - __repmovsb(&ctx->__fpustate, &cr->FltSave, sizeof(ctx->__fpustate)); - ctx->__fpustate.mxcsr = cr->MxCsr; -} - -textwindows void _ntlinux2context(struct NtContext *cr, const ucontext_t *ctx) { - if (!cr) - return; - cr->EFlags = ctx->uc_mcontext.eflags; - cr->Rax = ctx->uc_mcontext.rax; - cr->Rbx = ctx->uc_mcontext.rbx; - cr->Rcx = ctx->uc_mcontext.rcx; - cr->Rdx = ctx->uc_mcontext.rdx; - cr->Rdi = ctx->uc_mcontext.rdi; - cr->Rsi = ctx->uc_mcontext.rsi; - cr->Rbp = ctx->uc_mcontext.rbp; - cr->Rsp = ctx->uc_mcontext.rsp; - cr->Rip = ctx->uc_mcontext.rip; - cr->R8 = ctx->uc_mcontext.r8; - cr->R9 = ctx->uc_mcontext.r9; - cr->R10 = ctx->uc_mcontext.r10; - cr->R11 = ctx->uc_mcontext.r11; - cr->R12 = ctx->uc_mcontext.r12; - cr->R13 = ctx->uc_mcontext.r13; - cr->R14 = ctx->uc_mcontext.r14; - cr->R15 = ctx->uc_mcontext.r15; - cr->SegCs = ctx->uc_mcontext.cs; - cr->SegGs = ctx->uc_mcontext.gs; - cr->SegFs = ctx->uc_mcontext.fs; - cr->MxCsr = ctx->__fpustate.mxcsr; - __repmovsb(&cr->FltSave, &ctx->__fpustate, sizeof(ctx->__fpustate)); -} - -#endif /* __x86_64__ */ diff --git a/libc/intrin/printmaps.c b/libc/intrin/printmaps.c index 0747a50dc..7503876ed 100644 --- a/libc/intrin/printmaps.c +++ b/libc/intrin/printmaps.c @@ -16,39 +16,92 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/dce.h" #include "libc/fmt/conv.h" #include "libc/fmt/itoa.h" +#include "libc/intrin/bsr.h" #include "libc/intrin/describeflags.h" #include "libc/intrin/kprintf.h" #include "libc/intrin/maps.h" -#include "libc/macros.h" #include "libc/runtime/memtrack.internal.h" #include "libc/runtime/runtime.h" #include "libc/sysv/consts/auxv.h" -/** - * Prints memory mappings. - */ -void __print_maps(size_t limit) { - char mappingbuf[8], sb[16]; - __maps_lock(); +// this will usually return 12 since x86 pml4t uses a 47 bit address +// space in userspace, and decent arm machines uses a 48 bit address +// space. however it could go lower on embedded devices. it can also +// rise higher on expensive x86 machines with pml5t, if user uses it +static int get_address_digits(int pagesz) { + int max_bits = 0; for (struct Tree *e = tree_first(__maps.maps); e; e = tree_next(e)) { struct Map *map = MAP_TREE_CONTAINER(e); - kprintf("%012lx-%012lx %!s", map->addr, map->addr + map->size, + char *end = map->addr + ((map->size + pagesz - 1) & -pagesz); + int bits = bsrll((uintptr_t)end) + 1; + if (bits > max_bits) + max_bits = bits; + } + return ((max_bits + 3) & -4) / 4; +} + +/** + * Prints memory mappings known to cosmo. + */ +void __print_maps(size_t limit) { + __maps_lock(); + char sb[16]; + char mappingbuf[8]; + struct Map *last = 0; + int pagesz = __pagesize; + int gransz = __gransize; + int digs = get_address_digits(pagesz); + for (struct Tree *e = tree_first(__maps.maps); e; e = tree_next(e)) { + struct Map *map = MAP_TREE_CONTAINER(e); + + // show gaps between maps + if (last) { + char *beg = last->addr + ((last->size + gransz - 1) & -gransz); + char *end = map->addr; + if (end > beg) { + size_t gap = end - beg; + sizefmt(sb, gap, 1024); + kprintf("%0*lx-%0*lx %sb\n", digs, beg, digs, end, sb); + } + } + last = map; + + // show mapping + kprintf("%0*lx-%0*lx %!s", digs, map->addr, digs, map->addr + map->size, _DescribeMapping(mappingbuf, map->prot, map->flags)); sizefmt(sb, map->size, 1024); kprintf(" %!sb", sb); - if (map->hand && map->hand != -1) - kprintf(" hand=%ld", map->hand); + if (IsWindows()) { + switch (map->hand) { + case MAPS_RESERVATION: + kprintf(" reservation"); + break; + case MAPS_SUBREGION: + break; + case MAPS_VIRTUAL: + kprintf(" virtual"); + break; + default: + kprintf(" hand=%ld", map->hand); + break; + } + } if (map->iscow) kprintf(" cow"); if (map->readonlyfile) kprintf(" readonlyfile"); kprintf("\n"); + + // stay beneath our limit if (!--limit) break; } - kprintf("# %'zu bytes in %'zu mappings\n", __maps.pages * __pagesize, + + // print summary + kprintf("# %'zu bytes in %'zu mappings\n", __maps.pages * pagesz, __maps.count); __maps_unlock(); } diff --git a/libc/intrin/printmapswin32.c b/libc/intrin/printmapswin32.c new file mode 100644 index 000000000..8f03b7db0 --- /dev/null +++ b/libc/intrin/printmapswin32.c @@ -0,0 +1,71 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 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/fmt/conv.h" +#include "libc/intrin/describeflags.h" +#include "libc/intrin/kprintf.h" +#include "libc/macros.h" +#include "libc/nt/enum/memflags.h" +#include "libc/nt/memory.h" +#include "libc/runtime/runtime.h" +#include "libc/stdio/sysparam.h" +#include "libc/str/str.h" + +static const struct DescribeFlags kNtMemState[] = { + {kNtMemCommit, "Commit"}, // + {kNtMemFree, "Free"}, // + {kNtMemReserve, "Reserve"}, // +}; + +const char *DescribeNtMemState(char buf[64], uint32_t x) { + return _DescribeFlags(buf, 64, kNtMemState, ARRAYLEN(kNtMemState), "kNtMem", + x); +} + +static const struct DescribeFlags kNtMemType[] = { + {kNtMemImage, "Image"}, // + {kNtMemMapped, "Mapped"}, // + {kNtMemPrivate, "Private"}, // +}; + +const char *DescribeNtMemType(char buf[64], uint32_t x) { + return _DescribeFlags(buf, 64, kNtMemType, ARRAYLEN(kNtMemType), "kNtMem", x); +} + +void __print_maps_win32(int64_t hProcess, const char *addr, size_t size) { + char *p, b[5][64]; + struct NtMemoryBasicInformation mi; + kprintf("%-12s %-12s %10s %16s %16s %32s %32s\n", "Allocation", "BaseAddress", + "RegionSize", "State", "Type", "AllocationProtect", "Protect"); + for (p = 0;; p = (char *)mi.BaseAddress + mi.RegionSize) { + bzero(&mi, sizeof(mi)); + if (!VirtualQueryEx(hProcess, p, &mi, sizeof(mi))) + break; + sizefmt(b[0], mi.RegionSize, 1024); + kprintf("%.12lx %.12lx %10s %16s %16s %32s %32s%s\n", mi.AllocationBase, + mi.BaseAddress, b[0], DescribeNtMemState(b[1], mi.State), + DescribeNtMemType(b[2], mi.Type), + _DescribeNtPageFlags(b[3], mi.AllocationProtect), + _DescribeNtPageFlags(b[4], mi.Protect), + (mi.State != kNtMemFree && + MAX(addr, (const char *)mi.BaseAddress) < + MIN(addr + size, (const char *)mi.BaseAddress + mi.RegionSize)) + ? " [OVERLAPS]" + : ""); + } +} diff --git a/libc/intrin/pthread_mutex_lock.c b/libc/intrin/pthread_mutex_lock.c index 818fec3f2..8ee1daa12 100644 --- a/libc/intrin/pthread_mutex_lock.c +++ b/libc/intrin/pthread_mutex_lock.c @@ -17,85 +17,89 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/blockcancel.internal.h" -#include "libc/calls/calls.h" #include "libc/calls/state.internal.h" +#include "libc/cosmo.h" #include "libc/dce.h" #include "libc/errno.h" #include "libc/intrin/atomic.h" #include "libc/intrin/describeflags.h" +#include "libc/intrin/kprintf.h" #include "libc/intrin/strace.h" #include "libc/intrin/weaken.h" +#include "libc/macros.h" #include "libc/runtime/internal.h" #include "libc/thread/lock.h" +#include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" -#include "third_party/nsync/futex.internal.h" +#include "libc/thread/tls.h" #include "third_party/nsync/mu.h" -static void pthread_mutex_lock_spin(atomic_int *word) { - int backoff = 0; - if (atomic_exchange_explicit(word, 1, memory_order_acquire)) { - LOCKTRACE("acquiring pthread_mutex_lock_spin(%t)...", word); - for (;;) { - for (;;) { - if (!atomic_load_explicit(word, memory_order_relaxed)) - break; - backoff = pthread_delay_np(word, backoff); - } - if (!atomic_exchange_explicit(word, 1, memory_order_acquire)) - break; - } +static errno_t pthread_mutex_lock_normal_success(pthread_mutex_t *mutex, + uint64_t word) { + if (IsModeDbg() || MUTEX_TYPE(word) == PTHREAD_MUTEX_ERRORCHECK) { + __deadlock_track(mutex, MUTEX_TYPE(word) == PTHREAD_MUTEX_ERRORCHECK); + __deadlock_record(mutex, MUTEX_TYPE(word) == PTHREAD_MUTEX_ERRORCHECK); } + return 0; } // see "take 3" algorithm in "futexes are tricky" by ulrich drepper // slightly improved to attempt acquiring multiple times b4 syscall -static void pthread_mutex_lock_drepper(atomic_int *futex, char pshare) { - int word = 0; +static int pthread_mutex_lock_drepper(pthread_mutex_t *mutex, uint64_t word, + bool is_trylock) { + int val = 0; if (atomic_compare_exchange_strong_explicit( - futex, &word, 1, memory_order_acquire, memory_order_acquire)) - return; - LOCKTRACE("acquiring pthread_mutex_lock_drepper(%t)...", futex); - if (word == 1) - word = atomic_exchange_explicit(futex, 2, memory_order_acquire); + &mutex->_futex, &val, 1, memory_order_acquire, memory_order_acquire)) + return pthread_mutex_lock_normal_success(mutex, word); + if (is_trylock) + return EBUSY; + LOCKTRACE("acquiring pthread_mutex_lock_drepper(%t)...", mutex); + if (val == 1) + val = atomic_exchange_explicit(&mutex->_futex, 2, memory_order_acquire); BLOCK_CANCELATION; - while (word > 0) { - _weaken(nsync_futex_wait_)(futex, 2, pshare, 0, 0); - word = atomic_exchange_explicit(futex, 2, memory_order_acquire); + while (val > 0) { + cosmo_futex_wait(&mutex->_futex, 2, MUTEX_PSHARED(word), 0, 0); + val = atomic_exchange_explicit(&mutex->_futex, 2, memory_order_acquire); } ALLOW_CANCELATION; + return pthread_mutex_lock_normal_success(mutex, word); } static errno_t pthread_mutex_lock_recursive(pthread_mutex_t *mutex, - uint64_t word) { + uint64_t word, bool is_trylock) { uint64_t lock; int backoff = 0; - int me = gettid(); + int me = atomic_load_explicit(&__get_tls()->tib_ptid, memory_order_relaxed); bool once = false; for (;;) { if (MUTEX_OWNER(word) == me) { - if (MUTEX_TYPE(word) != PTHREAD_MUTEX_ERRORCHECK) { - if (MUTEX_DEPTH(word) < MUTEX_DEPTH_MAX) { - if (atomic_compare_exchange_weak_explicit( - &mutex->_word, &word, MUTEX_INC_DEPTH(word), - memory_order_relaxed, memory_order_relaxed)) - return 0; - continue; - } else { - return EAGAIN; - } + if (MUTEX_DEPTH(word) < MUTEX_DEPTH_MAX) { + if (atomic_compare_exchange_weak_explicit( + &mutex->_word, &word, MUTEX_INC_DEPTH(word), + memory_order_relaxed, memory_order_relaxed)) + return 0; + continue; } else { - return EDEADLK; + return EAGAIN; } } + if (IsModeDbg()) + __deadlock_check(mutex, 0); word = MUTEX_UNLOCK(word); lock = MUTEX_LOCK(word); lock = MUTEX_SET_OWNER(lock, me); if (atomic_compare_exchange_weak_explicit(&mutex->_word, &word, lock, memory_order_acquire, memory_order_relaxed)) { + if (IsModeDbg()) { + __deadlock_track(mutex, 0); + __deadlock_record(mutex, 0); + } mutex->_pid = __pid; return 0; } + if (is_trylock) + return EBUSY; if (!once) { LOCKTRACE("acquiring pthread_mutex_lock_recursive(%t)...", mutex); once = true; @@ -113,25 +117,33 @@ static errno_t pthread_mutex_lock_recursive(pthread_mutex_t *mutex, #if PTHREAD_USE_NSYNC static errno_t pthread_mutex_lock_recursive_nsync(pthread_mutex_t *mutex, - uint64_t word) { - int me = gettid(); + uint64_t word, + bool is_trylock) { + int me = atomic_load_explicit(&__get_tls()->tib_ptid, memory_order_relaxed); for (;;) { if (MUTEX_OWNER(word) == me) { - if (MUTEX_TYPE(word) != PTHREAD_MUTEX_ERRORCHECK) { - if (MUTEX_DEPTH(word) < MUTEX_DEPTH_MAX) { - if (atomic_compare_exchange_weak_explicit( - &mutex->_word, &word, MUTEX_INC_DEPTH(word), - memory_order_relaxed, memory_order_relaxed)) - return 0; - continue; - } else { - return EAGAIN; - } + if (MUTEX_DEPTH(word) < MUTEX_DEPTH_MAX) { + if (atomic_compare_exchange_weak_explicit( + &mutex->_word, &word, MUTEX_INC_DEPTH(word), + memory_order_relaxed, memory_order_relaxed)) + return 0; + continue; } else { - return EDEADLK; + return EAGAIN; } } - _weaken(nsync_mu_lock)((nsync_mu *)mutex->_nsyncx); + if (IsModeDbg()) + __deadlock_check(mutex, 0); + if (!is_trylock) { + _weaken(nsync_mu_lock)((nsync_mu *)mutex->_nsync); + } else { + if (!_weaken(nsync_mu_trylock)((nsync_mu *)mutex->_nsync)) + return EBUSY; + } + if (IsModeDbg()) { + __deadlock_track(mutex, 0); + __deadlock_record(mutex, 0); + } word = MUTEX_UNLOCK(word); word = MUTEX_LOCK(word); word = MUTEX_SET_OWNER(word, me); @@ -142,73 +154,82 @@ static errno_t pthread_mutex_lock_recursive_nsync(pthread_mutex_t *mutex, } #endif -static errno_t pthread_mutex_lock_impl(pthread_mutex_t *mutex) { - uint64_t word; +static errno_t pthread_mutex_lock_impl(pthread_mutex_t *mutex, + bool is_trylock) { + uint64_t word = atomic_load_explicit(&mutex->_word, memory_order_relaxed); - // get current state of lock - word = atomic_load_explicit(&mutex->_word, memory_order_relaxed); + // handle recursive mutexes + if (MUTEX_TYPE(word) == PTHREAD_MUTEX_RECURSIVE) { +#if PTHREAD_USE_NSYNC + if (_weaken(nsync_mu_lock) && + MUTEX_PSHARED(word) == PTHREAD_PROCESS_PRIVATE) { + return pthread_mutex_lock_recursive_nsync(mutex, word, is_trylock); + } else { + return pthread_mutex_lock_recursive(mutex, word, is_trylock); + } +#else + return pthread_mutex_lock_recursive(mutex, word, is_trylock); +#endif + } + + // check if normal mutex is already owned by calling thread + if (!is_trylock && + (MUTEX_TYPE(word) == PTHREAD_MUTEX_ERRORCHECK || + (IsModeDbg() && MUTEX_TYPE(word) == PTHREAD_MUTEX_DEFAULT))) { + if (__deadlock_tracked(mutex) == 1) { + if (IsModeDbg() && MUTEX_TYPE(word) != PTHREAD_MUTEX_ERRORCHECK) { + kprintf("error: attempted to lock non-recursive mutex that's already " + "held by the calling thread: %t\n", + mutex); + DebugBreak(); + } + return EDEADLK; + } + } + + // check if locking will create cycle in lock graph + if (IsModeDbg() || MUTEX_TYPE(word) == PTHREAD_MUTEX_ERRORCHECK) + if (__deadlock_check(mutex, MUTEX_TYPE(word) == PTHREAD_MUTEX_ERRORCHECK)) + return EDEADLK; #if PTHREAD_USE_NSYNC // use superior mutexes if possible - if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL && // - MUTEX_PSHARED(word) == PTHREAD_PROCESS_PRIVATE && // + if (MUTEX_PSHARED(word) == PTHREAD_PROCESS_PRIVATE && _weaken(nsync_mu_lock)) { // on apple silicon we should just put our faith in ulock // otherwise *nsync gets struck down by the eye of sauron if (!IsXnuSilicon()) { - _weaken(nsync_mu_lock)((nsync_mu *)mutex); - return 0; + if (!is_trylock) { + _weaken(nsync_mu_lock)((nsync_mu *)mutex->_nsync); + return pthread_mutex_lock_normal_success(mutex, word); + } else { + if (_weaken(nsync_mu_trylock)((nsync_mu *)mutex->_nsync)) + return pthread_mutex_lock_normal_success(mutex, word); + return EBUSY; + } } } #endif - // handle normal mutexes - if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL) { - if (_weaken(nsync_futex_wait_)) { - pthread_mutex_lock_drepper(&mutex->_futex, MUTEX_PSHARED(word)); - } else { - pthread_mutex_lock_spin(&mutex->_futex); - } - return 0; - } - -// handle recursive and error checking mutexes -#if PTHREAD_USE_NSYNC - if (_weaken(nsync_mu_lock) && - MUTEX_PSHARED(word) == PTHREAD_PROCESS_PRIVATE) { - return pthread_mutex_lock_recursive_nsync(mutex, word); - } else { - return pthread_mutex_lock_recursive(mutex, word); - } -#else - return pthread_mutex_lock_recursive(mutex, word); -#endif + // isc licensed non-recursive mutex implementation + return pthread_mutex_lock_drepper(mutex, word, is_trylock); } /** - * Locks mutex. - * - * Here's an example of using a normal mutex: + * Locks mutex, e.g. * * pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; * pthread_mutex_lock(&lock); * // do work... * pthread_mutex_unlock(&lock); - * pthread_mutex_destroy(&lock); * - * Cosmopolitan permits succinct notation for normal mutexes: - * - * pthread_mutex_t lock = {0}; - * pthread_mutex_lock(&lock); - * // do work... - * pthread_mutex_unlock(&lock); - * - * Here's an example of the proper way to do recursive mutexes: + * The long way to do that is: * * pthread_mutex_t lock; * pthread_mutexattr_t attr; * pthread_mutexattr_init(&attr); - * pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + * pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); + * pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE); * pthread_mutex_init(&lock, &attr); * pthread_mutexattr_destroy(&attr); * pthread_mutex_lock(&lock); @@ -216,28 +237,101 @@ static errno_t pthread_mutex_lock_impl(pthread_mutex_t *mutex) { * pthread_mutex_unlock(&lock); * pthread_mutex_destroy(&lock); * - * This function does nothing in vfork() children. + * The following non-POSIX initializers are also provided by cosmo libc: * - * You can debug locks the acquisition of locks by building your program - * with `cosmocc -mdbg` and passing the `--strace` flag to your program. - * This will cause a line to be logged each time a mutex or spin lock is - * locked or unlocked. When locking, this is printed after the lock gets - * acquired. The entry to the lock operation will be logged too but only - * if the lock couldn't be immediately acquired. Lock logging works best - * when `mutex` refers to a static variable, in which case its name will - * be printed in the log. + * - `PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP` + * - `PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP` + * - `PTHREAD_NORMAL_MUTEX_INITIALIZER_NP` + * + * Locking a mutex that's already locked by the calling thread will make + * the thread hang indefinitely, i.e. it's a deadlock condition. You can + * use `PTHREAD_MUTEX_RECURSIVE` to allow recursive locking, which could + * result in somewhat less performance. An alternative solution is using + * the `PTHREAD_MUTEX_ERRORCHECK` mode, which raises `EDEADLK` for that. + * + * If a thread locks a mutex while other mutexes are already locked then + * you need to observe a consistent global ordering, otherwise deadlocks + * might occur. The Cosmopolitan runtime can detect these cycles quickly + * so you can fix your code before it becomes an issue. With error check + * mode, an EPERM will be returned. If your app is using `cosmocc -mdbg` + * then an error message will be printed including the demangled symbols + * of the mutexes in the strongly connected component that was detected. + * Please note that, even for debug builds mutexes set to explicitly use + * the `PTHREAD_MUTEX_ERRORCHECK` mode will return an error code instead + * which means the cosmo debug mode only influences undefined behaviors. + * + * Cosmopolitan only supports error checking on mutexes stored in static + * memory, i.e. your `mutex` pointer must point inside the .data or .bss + * sections of your executable. When compiling your programs using -mdbg + * all your locks will gain error checking automatically. When deadlocks + * are detected an error message will be printed and a SIGTRAP signal is + * raised, which may be ignored to force EDEADLK and EPERM to be raised. + * + * Using `cosmocc -mdbg` also enhances `--strace` with information about + * mutexes. First, locks and unlocks will be logged. Since the lock line + * only appears after the lock is acquired, that might mean you'll never + * get an indication about a lock that takes a very long time to acquire + * so, whenever a lock can't immediately be acquired, a second line gets + * printed *before* the lock is acquired to let you know that the thread + * is waiting for a particular lock. If your mutex object resides within + * static memory, then its demangled symbol name will be printed. If you + * call ShowCrashReports() at the beginning of your main() function then + * you'll also see a backtrace when a locking violation occurs. When the + * symbols in the violation error messages show up as numbers, and it is + * desirable to see demangled symbols without enabling full crash report + * functionality the GetSymbolTable() function may be called for effect. + * + * If you use `PTHREAD_MUTEX_NORMAL`, instead of `PTHREAD_MUTEX_DEFAULT` + * then deadlocking is actually defined behavior according to POSIX.1 so + * the helpfulness of `cosmocc -mdbg` will be somewhat weakened. + * + * If your `mutex` object resides in `MAP_SHARED` memory, then undefined + * behavior will happen unless you use `PTHREAD_PROCESS_SHARED` mode, if + * the lock is used by multiple processes. + * + * This function does nothing when the process is in vfork() mode. * * @return 0 on success, or error number on failure + * @raise EDEADLK if mutex is recursive and locked by another thread + * @raise EDEADLK if mutex is non-recursive and locked by current thread + * @raise EDEADLK if cycle is detected in global nested lock graph + * @raise EAGAIN if maximum recursive locks is exceeded * @see pthread_spin_lock() * @vforksafe */ -errno_t pthread_mutex_lock(pthread_mutex_t *mutex) { - if (!__vforked) { - errno_t err = pthread_mutex_lock_impl(mutex); +errno_t _pthread_mutex_lock(pthread_mutex_t *mutex) { + if (__tls_enabled && !__vforked) { + errno_t err = pthread_mutex_lock_impl(mutex, false); LOCKTRACE("pthread_mutex_lock(%t) → %s", mutex, DescribeErrno(err)); return err; } else { - LOCKTRACE("skipping pthread_mutex_lock(%t) due to vfork", mutex); + LOCKTRACE("skipping pthread_mutex_lock(%t) due to runtime state", mutex); return 0; } } + +/** + * Attempts acquiring lock. + * + * Unlike pthread_mutex_lock() this function won't block and instead + * returns an error immediately if the lock couldn't be acquired. + * + * @return 0 if lock was acquired, otherwise an errno + * @raise EBUSY if lock is currently held by another thread + * @raise EAGAIN if maximum number of recursive locks is held + * @raise EDEADLK if `mutex` is `PTHREAD_MUTEX_ERRORCHECK` and the + * current thread already holds this mutex + */ +errno_t _pthread_mutex_trylock(pthread_mutex_t *mutex) { + if (__tls_enabled && !__vforked) { + errno_t err = pthread_mutex_lock_impl(mutex, true); + LOCKTRACE("pthread_mutex_trylock(%t) → %s", mutex, DescribeErrno(err)); + return err; + } else { + LOCKTRACE("skipping pthread_mutex_trylock(%t) due to runtime state", mutex); + return 0; + } +} + +__weak_reference(_pthread_mutex_lock, pthread_mutex_lock); +__weak_reference(_pthread_mutex_trylock, pthread_mutex_trylock); diff --git a/libc/intrin/pthread_mutex_trylock.c b/libc/intrin/pthread_mutex_trylock.c deleted file mode 100644 index 39607de5f..000000000 --- a/libc/intrin/pthread_mutex_trylock.c +++ /dev/null @@ -1,164 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2023 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/dce.h" -#include "libc/errno.h" -#include "libc/intrin/atomic.h" -#include "libc/intrin/weaken.h" -#include "libc/runtime/internal.h" -#include "libc/thread/lock.h" -#include "libc/thread/thread.h" -#include "third_party/nsync/futex.internal.h" -#include "third_party/nsync/mu.h" - -static errno_t pthread_mutex_trylock_spin(atomic_int *word) { - if (!atomic_exchange_explicit(word, 1, memory_order_acquire)) - return 0; - return EBUSY; -} - -static errno_t pthread_mutex_trylock_drepper(atomic_int *futex) { - int word = 0; - if (atomic_compare_exchange_strong_explicit( - futex, &word, 1, memory_order_acquire, memory_order_acquire)) - return 0; - return EBUSY; -} - -static errno_t pthread_mutex_trylock_recursive(pthread_mutex_t *mutex, - uint64_t word) { - uint64_t lock; - int me = gettid(); - for (;;) { - if (MUTEX_OWNER(word) == me) { - if (MUTEX_TYPE(word) != PTHREAD_MUTEX_ERRORCHECK) { - if (MUTEX_DEPTH(word) < MUTEX_DEPTH_MAX) { - if (atomic_compare_exchange_weak_explicit( - &mutex->_word, &word, MUTEX_INC_DEPTH(word), - memory_order_relaxed, memory_order_relaxed)) - return 0; - continue; - } else { - return EAGAIN; - } - } else { - return EDEADLK; - } - } - word = MUTEX_UNLOCK(word); - lock = MUTEX_LOCK(word); - lock = MUTEX_SET_OWNER(lock, me); - if (atomic_compare_exchange_weak_explicit(&mutex->_word, &word, lock, - memory_order_acquire, - memory_order_relaxed)) { - mutex->_pid = __pid; - return 0; - } - return EBUSY; - } -} - -static errno_t pthread_mutex_trylock_recursive_nsync(pthread_mutex_t *mutex, - uint64_t word) { - int me = gettid(); - for (;;) { - if (MUTEX_OWNER(word) == me) { - if (MUTEX_TYPE(word) != PTHREAD_MUTEX_ERRORCHECK) { - if (MUTEX_DEPTH(word) < MUTEX_DEPTH_MAX) { - if (atomic_compare_exchange_weak_explicit( - &mutex->_word, &word, MUTEX_INC_DEPTH(word), - memory_order_relaxed, memory_order_relaxed)) - return 0; - continue; - } else { - return EAGAIN; - } - } else { - return EDEADLK; - } - } - if (_weaken(nsync_mu_trylock)((nsync_mu *)mutex->_nsyncx)) { - word = MUTEX_UNLOCK(word); - word = MUTEX_LOCK(word); - word = MUTEX_SET_OWNER(word, me); - mutex->_word = word; - mutex->_pid = __pid; - return 0; - } else { - return EBUSY; - } - } -} - -/** - * Attempts acquiring lock. - * - * Unlike pthread_mutex_lock() this function won't block and instead - * returns an error immediately if the lock couldn't be acquired. - * - * @return 0 if lock was acquired, otherwise an errno - * @raise EAGAIN if maximum number of recursive locks is held - * @raise EBUSY if lock is currently held in read or write mode - * @raise EINVAL if `mutex` doesn't refer to an initialized lock - * @raise EDEADLK if `mutex` is `PTHREAD_MUTEX_ERRORCHECK` and the - * current thread already holds this mutex - */ -errno_t pthread_mutex_trylock(pthread_mutex_t *mutex) { - - // get current state of lock - uint64_t word = atomic_load_explicit(&mutex->_word, memory_order_relaxed); - -#if PTHREAD_USE_NSYNC - // use superior mutexes if possible - if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL && - MUTEX_PSHARED(word) == PTHREAD_PROCESS_PRIVATE && // - _weaken(nsync_mu_trylock)) { - // on apple silicon we should just put our faith in ulock - // otherwise *nsync gets struck down by the eye of sauron - if (!IsXnuSilicon()) { - if (_weaken(nsync_mu_trylock)((nsync_mu *)mutex)) { - return 0; - } else { - return EBUSY; - } - } - } -#endif - - // handle normal mutexes - if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL) { - if (_weaken(nsync_futex_wait_)) { - return pthread_mutex_trylock_drepper(&mutex->_futex); - } else { - return pthread_mutex_trylock_spin(&mutex->_futex); - } - } - - // handle recursive and error checking mutexes -#if PTHREAD_USE_NSYNC - if (_weaken(nsync_mu_trylock) && - MUTEX_PSHARED(word) == PTHREAD_PROCESS_PRIVATE) { - return pthread_mutex_trylock_recursive_nsync(mutex, word); - } else { - return pthread_mutex_trylock_recursive(mutex, word); - } -#else - return pthread_mutex_trylock_recursive(mutex, word); -#endif -} diff --git a/libc/intrin/pthread_mutex_unlock.c b/libc/intrin/pthread_mutex_unlock.c index ec9a90cae..25525dccb 100644 --- a/libc/intrin/pthread_mutex_unlock.c +++ b/libc/intrin/pthread_mutex_unlock.c @@ -18,33 +18,33 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/calls/state.internal.h" +#include "libc/cosmo.h" #include "libc/dce.h" #include "libc/errno.h" #include "libc/intrin/atomic.h" +#include "libc/intrin/describeflags.h" +#include "libc/intrin/kprintf.h" #include "libc/intrin/strace.h" #include "libc/intrin/weaken.h" #include "libc/runtime/internal.h" #include "libc/thread/lock.h" +#include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" -#include "third_party/nsync/futex.internal.h" +#include "libc/thread/tls.h" #include "third_party/nsync/mu.h" -static void pthread_mutex_unlock_spin(atomic_int *word) { - atomic_store_explicit(word, 0, memory_order_release); -} - // see "take 3" algorithm in "futexes are tricky" by ulrich drepper static void pthread_mutex_unlock_drepper(atomic_int *futex, char pshare) { int word = atomic_fetch_sub_explicit(futex, 1, memory_order_release); if (word == 2) { atomic_store_explicit(futex, 0, memory_order_release); - _weaken(nsync_futex_wake_)(futex, 1, pshare); + cosmo_futex_wake(futex, 1, pshare); } } static errno_t pthread_mutex_unlock_recursive(pthread_mutex_t *mutex, uint64_t word) { - int me = gettid(); + int me = atomic_load_explicit(&__get_tls()->tib_ptid, memory_order_relaxed); for (;;) { // we allow unlocking an initialized lock that wasn't locked, but we @@ -65,15 +65,18 @@ static errno_t pthread_mutex_unlock_recursive(pthread_mutex_t *mutex, // actually unlock the mutex if (atomic_compare_exchange_weak_explicit( &mutex->_word, &word, MUTEX_UNLOCK(word), memory_order_release, - memory_order_relaxed)) + memory_order_relaxed)) { + if (IsModeDbg()) + __deadlock_untrack(mutex); return 0; + } } } #if PTHREAD_USE_NSYNC static errno_t pthread_mutex_unlock_recursive_nsync(pthread_mutex_t *mutex, uint64_t word) { - int me = gettid(); + int me = atomic_load_explicit(&__get_tls()->tib_ptid, memory_order_relaxed); for (;;) { // we allow unlocking an initialized lock that wasn't locked, but we @@ -93,67 +96,87 @@ static errno_t pthread_mutex_unlock_recursive_nsync(pthread_mutex_t *mutex, // actually unlock the mutex mutex->_word = MUTEX_UNLOCK(word); - _weaken(nsync_mu_unlock)((nsync_mu *)mutex->_nsyncx); + _weaken(nsync_mu_unlock)((nsync_mu *)mutex->_nsync); + if (IsModeDbg()) + __deadlock_untrack(mutex); return 0; } } #endif -/** - * Releases mutex. - * - * This function does nothing in vfork() children. - * - * @return 0 on success or error number on failure - * @raises EPERM if in error check mode and not owned by caller - * @vforksafe - */ -errno_t pthread_mutex_unlock(pthread_mutex_t *mutex) { - uint64_t word; +static errno_t pthread_mutex_unlock_impl(pthread_mutex_t *mutex) { + uint64_t word = atomic_load_explicit(&mutex->_word, memory_order_relaxed); - if (__vforked) { - LOCKTRACE("skipping pthread_mutex_lock(%t) due to vfork", mutex); - return 0; + // check if mutex isn't held by calling thread + if (MUTEX_TYPE(word) == PTHREAD_MUTEX_ERRORCHECK || IsModeDbg()) { + if (__deadlock_tracked(mutex) == 0) { + if (IsModeDbg() && MUTEX_TYPE(word) != PTHREAD_MUTEX_ERRORCHECK) { + kprintf("error: unlock mutex not owned by calling thread: %t\n", mutex); + DebugBreak(); + } + return EPERM; + } } - LOCKTRACE("pthread_mutex_unlock(%t)", mutex); - - // get current state of lock - word = atomic_load_explicit(&mutex->_word, memory_order_relaxed); + // handle recursive mutexes + if (MUTEX_TYPE(word) == PTHREAD_MUTEX_RECURSIVE) { +#if PTHREAD_USE_NSYNC + if (_weaken(nsync_mu_unlock) && + MUTEX_PSHARED(word) == PTHREAD_PROCESS_PRIVATE) { + return pthread_mutex_unlock_recursive_nsync(mutex, word); + } else { + return pthread_mutex_unlock_recursive(mutex, word); + } +#else + return pthread_mutex_unlock_recursive(mutex, word); +#endif + } #if PTHREAD_USE_NSYNC // use superior mutexes if possible - if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL && // - MUTEX_PSHARED(word) == PTHREAD_PROCESS_PRIVATE && // + if (MUTEX_PSHARED(word) == PTHREAD_PROCESS_PRIVATE && // _weaken(nsync_mu_unlock)) { // on apple silicon we should just put our faith in ulock // otherwise *nsync gets struck down by the eye of sauron if (!IsXnuSilicon()) { - _weaken(nsync_mu_unlock)((nsync_mu *)mutex); + _weaken(nsync_mu_unlock)((nsync_mu *)mutex->_nsync); + if (MUTEX_TYPE(word) == PTHREAD_MUTEX_ERRORCHECK || IsModeDbg()) + __deadlock_untrack(mutex); return 0; } } #endif // implement barebones normal mutexes - if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL) { - if (_weaken(nsync_futex_wake_)) { - pthread_mutex_unlock_drepper(&mutex->_futex, MUTEX_PSHARED(word)); - } else { - pthread_mutex_unlock_spin(&mutex->_futex); - } + pthread_mutex_unlock_drepper(&mutex->_futex, MUTEX_PSHARED(word)); + if (MUTEX_TYPE(word) == PTHREAD_MUTEX_ERRORCHECK || IsModeDbg()) + __deadlock_untrack(mutex); + return 0; +} + +/** + * Releases mutex. + * + * POSIX.1 says it's undefined behavior to unlock a mutex that wasn't + * locked by the calling thread. Therefore, if `mutex` isn't locked, or + * it is locked and the thing that locked it was a different thread or + * process, then you should expect your program to deadlock or crash. + * + * This function does nothing in vfork() children. + * + * @return 0 on success or error number on failure + * @raises EPERM if mutex ownership isn't acceptable + * @vforksafe + */ +errno_t _pthread_mutex_unlock(pthread_mutex_t *mutex) { + if (__tls_enabled && !__vforked) { + errno_t err = pthread_mutex_unlock_impl(mutex); + LOCKTRACE("pthread_mutex_unlock(%t) → %s", mutex, DescribeErrno(err)); + return err; + } else { + LOCKTRACE("skipping pthread_mutex_lock(%t) due to runtime state", mutex); return 0; } - - // handle recursive and error checking mutexes -#if PTHREAD_USE_NSYNC - if (_weaken(nsync_mu_unlock) && - MUTEX_PSHARED(word) == PTHREAD_PROCESS_PRIVATE) { - return pthread_mutex_unlock_recursive_nsync(mutex, word); - } else { - return pthread_mutex_unlock_recursive(mutex, word); - } -#else - return pthread_mutex_unlock_recursive(mutex, word); -#endif } + +__weak_reference(_pthread_mutex_unlock, pthread_mutex_unlock); diff --git a/libc/intrin/pthread_mutex_wipe_np.c b/libc/intrin/pthread_mutex_wipe_np.c new file mode 100644 index 000000000..9c19f6d0a --- /dev/null +++ b/libc/intrin/pthread_mutex_wipe_np.c @@ -0,0 +1,39 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/atomic.h" +#include "libc/str/str.h" +#include "libc/thread/lock.h" +#include "libc/thread/posixthread.internal.h" +#include "libc/thread/thread.h" + +/** + * Unlocks mutex from child process after fork. + */ +int _pthread_mutex_wipe_np(pthread_mutex_t *mutex) { + atomic_init(&mutex->_word, MUTEX_UNLOCK(atomic_load_explicit( + &mutex->_word, memory_order_relaxed))); + atomic_init(&mutex->_futex, 0); + mutex->_pid = 0; + mutex->_nsync[0] = 0; + atomic_signal_fence(memory_order_relaxed); // avoid xmm + mutex->_nsync[1] = 0; + return 0; +} + +__weak_reference(_pthread_mutex_wipe_np, pthread_mutex_wipe_np); diff --git a/libc/intrin/pthread_orphan_np.c b/libc/intrin/pthread_orphan_np.c index 68e2a9f5f..1575502f1 100644 --- a/libc/intrin/pthread_orphan_np.c +++ b/libc/intrin/pthread_orphan_np.c @@ -16,6 +16,8 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" +#include "libc/intrin/atomic.h" #include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" @@ -28,5 +30,6 @@ int pthread_orphan_np(void) { res = _pthread_list == _pthread_list->prev && _pthread_list == _pthread_list->next; _pthread_unlock(); + unassert(!res || atomic_load(&_pthread_count) <= 1); return res; } diff --git a/libc/intrin/pthread_setcancelstate.c b/libc/intrin/pthread_setcancelstate.c index 9ce15824d..6e2a35f35 100644 --- a/libc/intrin/pthread_setcancelstate.c +++ b/libc/intrin/pthread_setcancelstate.c @@ -47,28 +47,30 @@ * @asyncsignalsafe */ errno_t pthread_setcancelstate(int state, int *oldstate) { + int old; errno_t err; struct PosixThread *pt; if (__tls_enabled && (pt = _pthread_self())) { + if (pt->pt_flags & PT_NOCANCEL) { + old = PTHREAD_CANCEL_DISABLE; + } else if (pt->pt_flags & PT_MASKED) { + old = PTHREAD_CANCEL_MASKED; + } else { + old = PTHREAD_CANCEL_ENABLE; + } switch (state) { case PTHREAD_CANCEL_ENABLE: - case PTHREAD_CANCEL_DISABLE: - case PTHREAD_CANCEL_MASKED: - if (oldstate) { - if (pt->pt_flags & PT_NOCANCEL) { - *oldstate = PTHREAD_CANCEL_DISABLE; - } else if (pt->pt_flags & PT_MASKED) { - *oldstate = PTHREAD_CANCEL_MASKED; - } else { - *oldstate = PTHREAD_CANCEL_ENABLE; - } - } pt->pt_flags &= ~(PT_NOCANCEL | PT_MASKED); - if (state == PTHREAD_CANCEL_MASKED) { - pt->pt_flags |= PT_MASKED; - } else if (state == PTHREAD_CANCEL_DISABLE) { - pt->pt_flags |= PT_NOCANCEL; - } + err = 0; + break; + case PTHREAD_CANCEL_DISABLE: + pt->pt_flags &= ~(PT_NOCANCEL | PT_MASKED); + pt->pt_flags |= PT_NOCANCEL; + err = 0; + break; + case PTHREAD_CANCEL_MASKED: + pt->pt_flags &= ~(PT_NOCANCEL | PT_MASKED); + pt->pt_flags |= PT_MASKED; err = 0; break; default: @@ -76,12 +78,13 @@ errno_t pthread_setcancelstate(int state, int *oldstate) { break; } } else { - if (oldstate) { - *oldstate = 0; - } + old = 0; err = 0; } -#if IsModeDbg() + if (!err) + if (oldstate) + *oldstate = old; +#if IsModeDbg() && 0 STRACE("pthread_setcancelstate(%s, [%s]) → %s", DescribeCancelState(0, &state), DescribeCancelState(err, oldstate), DescribeErrno(err)); diff --git a/libc/intrin/pthread_tid.c b/libc/intrin/pthread_tid.c index 4f7553e9a..fb9d22f44 100644 --- a/libc/intrin/pthread_tid.c +++ b/libc/intrin/pthread_tid.c @@ -21,9 +21,25 @@ #include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" +// +// - tib_ptid: always guaranteed to be non-zero in thread itself. on +// some platforms (e.g. xnu) the parent thread and other +// threads may need to wait for this value to be set. this +// is generally the value you want to read to get the tid. +// +// - tib_ctid: starts off as -1. once thread starts, it's set to the +// thread's tid before calling the thread callback. when +// thread is done executing, this is set to zero, and then +// this address is futex woken, in case the parent thread or +// any other thread is waiting on its completion. when a +// thread wants to read its own tid, it shouldn't use this, +// because the thread might need to do things after clearing +// its own tib_ctid (see pthread_exit() for static thread). +// int _pthread_tid(struct PosixThread *pt) { int tid = 0; - while (pt && !(tid = atomic_load_explicit(&pt->ptid, memory_order_acquire))) + while (pt && !(tid = atomic_load_explicit(&pt->tib->tib_ptid, + memory_order_acquire))) pthread_yield_np(); return tid; } diff --git a/libc/intrin/pthreadlock.c b/libc/intrin/pthreadlock.c index c7ef23ae4..085f5bba0 100644 --- a/libc/intrin/pthreadlock.c +++ b/libc/intrin/pthreadlock.c @@ -16,14 +16,15 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/stdalign.h" #include "libc/thread/posixthread.internal.h" -pthread_mutex_t _pthread_lock_obj = PTHREAD_MUTEX_INITIALIZER; +alignas(64) pthread_mutex_t __pthread_lock_obj = PTHREAD_MUTEX_INITIALIZER; void _pthread_lock(void) { - pthread_mutex_lock(&_pthread_lock_obj); + _pthread_mutex_lock(&__pthread_lock_obj); } void _pthread_unlock(void) { - pthread_mutex_unlock(&_pthread_lock_obj); + _pthread_mutex_unlock(&__pthread_lock_obj); } diff --git a/libc/intrin/rand64.c b/libc/intrin/rand64.c index e6aa1fd20..53252327e 100644 --- a/libc/intrin/rand64.c +++ b/libc/intrin/rand64.c @@ -22,12 +22,25 @@ #include "libc/runtime/runtime.h" #include "libc/str/str.h" #include "libc/sysv/consts/auxv.h" +#include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" #include "libc/thread/tls.h" static int _rand64_pid; static unsigned __int128 _rand64_pool; -pthread_mutex_t _rand64_lock_obj = PTHREAD_SIGNAL_SAFE_MUTEX_INITIALIZER_NP; +static pthread_mutex_t __rand64_lock_obj = PTHREAD_MUTEX_INITIALIZER; + +void __rand64_lock(void) { + _pthread_mutex_lock(&__rand64_lock_obj); +} + +void __rand64_unlock(void) { + _pthread_mutex_unlock(&__rand64_lock_obj); +} + +void __rand64_wipe(void) { + _pthread_mutex_wipe_np(&__rand64_lock_obj); +} /** * Returns nondeterministic random data. @@ -38,12 +51,11 @@ pthread_mutex_t _rand64_lock_obj = PTHREAD_SIGNAL_SAFE_MUTEX_INITIALIZER_NP; * * @see rdseed(), rdrand(), rand(), random(), rngset() * @note this function passes bigcrush and practrand - * @asyncsignalsafe */ uint64_t _rand64(void) { void *p; uint128_t s; - pthread_mutex_lock(&_rand64_lock_obj); + __rand64_lock(); if (__pid == _rand64_pid) { s = _rand64_pool; // normal path } else { @@ -64,6 +76,6 @@ uint64_t _rand64(void) { _rand64_pid = __pid; } _rand64_pool = (s *= 15750249268501108917ull); // lemur64 - pthread_mutex_unlock(&_rand64_lock_obj); + __rand64_unlock(); return s >> 64; } diff --git a/libc/calls/restore.S b/libc/intrin/restore.S similarity index 100% rename from libc/calls/restore.S rename to libc/intrin/restore.S diff --git a/libc/intrin/rlimit.h b/libc/intrin/rlimit.h new file mode 100644 index 000000000..05d0fb96e --- /dev/null +++ b/libc/intrin/rlimit.h @@ -0,0 +1,10 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_RLIMIT_H_ +#define COSMOPOLITAN_LIBC_INTRIN_RLIMIT_H_ +#include "libc/calls/struct/rlimit.h" +COSMOPOLITAN_C_START_ + +void __rlimit_stack_set(struct rlimit); +struct rlimit __rlimit_stack_get(void); + +COSMOPOLITAN_C_END_ +#endif /* COSMOPOLITAN_LIBC_INTRIN_RLIMIT_H_ */ diff --git a/libc/intrin/pthread_atfork.c b/libc/intrin/rlimitstack.c similarity index 50% rename from libc/intrin/pthread_atfork.c rename to libc/intrin/rlimitstack.c index 5093ed594..66f47c64a 100644 --- a/libc/intrin/pthread_atfork.c +++ b/libc/intrin/rlimitstack.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2022 Justine Alexandra Roberts Tunney │ +│ Copyright 2024 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,61 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/weaken.h" -#include "libc/thread/posixthread.internal.h" -#include "libc/thread/thread.h" +#include "libc/atomic.h" +#include "libc/calls/struct/rlimit.h" +#include "libc/calls/struct/rlimit.internal.h" +#include "libc/cosmo.h" +#include "libc/dce.h" +#include "libc/intrin/cxaatexit.h" +#include "libc/intrin/lockless.h" +#include "libc/intrin/rlimit.h" +#include "libc/runtime/stack.h" +#include "libc/sysv/consts/rlim.h" +#include "libc/sysv/consts/rlimit.h" -/** - * Registers fork() handlers. - * - * Parent and child functions are called in the same order they're - * registered. Prepare functions are called in reverse order. - * - * Here's an example of how pthread_atfork() can be used: - * - * static struct { - * pthread_once_t once; - * pthread_mutex_t lock; - * // data structures... - * } g_lib; - * - * static void lib_wipe(void) { - * pthread_mutex_init(&g_lib.lock, 0); - * } - * - * static void lib_lock(void) { - * pthread_mutex_lock(&g_lib.lock); - * } - * - * static void lib_unlock(void) { - * pthread_mutex_unlock(&g_lib.lock); - * } - * - * static void lib_setup(void) { - * lib_wipe(); - * pthread_atfork(lib_lock, lib_unlock, lib_wipe); - * } - * - * static void lib_init(void) { - * pthread_once(&g_lib.once, lib_setup); - * } - * - * void lib(void) { - * lib_init(); - * lib_lock(); - * // do stuff... - * lib_unlock(); - * } - * - * @param prepare is run by fork() before forking happens - * @param parent is run by fork() after forking happens in parent process - * @param child is run by fork() after forking happens in childe process - * @return 0 on success, or errno on error - * @raise ENOMEM if we require more vespene gas - */ -int pthread_atfork(atfork_f prepare, atfork_f parent, atfork_f child) { - if (_weaken(_pthread_atfork)) { - return _weaken(_pthread_atfork)(prepare, parent, child); +struct atomic_rlimit { + atomic_ulong cur; + atomic_ulong max; + atomic_uint once; + atomic_uint gen; +}; + +static struct atomic_rlimit __rlimit_stack; + +static void __rlimit_stack_init(void) { + struct rlimit rlim; + if (IsWindows()) { + rlim.rlim_cur = GetStaticStackSize(); + rlim.rlim_max = -1; // RLIM_INFINITY in consts.sh } else { - return 0; + sys_getrlimit(RLIMIT_STACK, &rlim); } + atomic_init(&__rlimit_stack.cur, rlim.rlim_cur); + atomic_init(&__rlimit_stack.max, rlim.rlim_max); +} + +struct rlimit __rlimit_stack_get(void) { + unsigned gen; + unsigned long cur, max; + cosmo_once(&__rlimit_stack.once, __rlimit_stack_init); + gen = lockless_read_begin(&__rlimit_stack.gen); + do { + cur = atomic_load_explicit(&__rlimit_stack.cur, memory_order_acquire); + max = atomic_load_explicit(&__rlimit_stack.max, memory_order_acquire); + } while (!lockless_read_end(&__rlimit_stack.gen, &gen)); + return (struct rlimit){cur, max}; +} + +void __rlimit_stack_set(struct rlimit rlim) { + unsigned gen; + unsigned long cur, max; + cosmo_once(&__rlimit_stack.once, __rlimit_stack_init); + __cxa_lock(); + cur = rlim.rlim_cur; + max = rlim.rlim_max; + gen = lockless_write_begin(&__rlimit_stack.gen); + atomic_store_explicit(&__rlimit_stack.cur, cur, memory_order_release); + atomic_store_explicit(&__rlimit_stack.max, max, memory_order_release); + lockless_write_end(&__rlimit_stack.gen, gen); + __cxa_unlock(); } diff --git a/libc/intrin/sig.c b/libc/intrin/sig.c index 8679b811d..0cf56902d 100644 --- a/libc/intrin/sig.c +++ b/libc/intrin/sig.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2023 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,38 +17,812 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/sysv/consts/sig.h" +#include "ape/sections.internal.h" +#include "libc/calls/calls.h" +#include "libc/calls/internal.h" #include "libc/calls/sig.internal.h" +#include "libc/calls/state.internal.h" +#include "libc/calls/struct/sigaction.h" +#include "libc/calls/struct/siginfo.h" #include "libc/calls/struct/sigset.internal.h" +#include "libc/calls/struct/ucontext.internal.h" +#include "libc/calls/syscall_support-nt.internal.h" +#include "libc/calls/ucontext.h" #include "libc/dce.h" +#include "libc/errno.h" #include "libc/intrin/atomic.h" +#include "libc/intrin/bsf.h" +#include "libc/intrin/describebacktrace.h" +#include "libc/intrin/dll.h" +#include "libc/intrin/maps.h" +#include "libc/intrin/nomultics.h" +#include "libc/intrin/strace.h" #include "libc/intrin/weaken.h" -#include "libc/thread/tls.h" +#include "libc/log/libfatal.internal.h" +#include "libc/mem/alloca.h" +#include "libc/nt/console.h" +#include "libc/nt/enum/context.h" +#include "libc/nt/enum/exceptionhandleractions.h" +#include "libc/nt/enum/pageflags.h" +#include "libc/nt/enum/processcreationflags.h" +#include "libc/nt/enum/signal.h" +#include "libc/nt/enum/status.h" +#include "libc/nt/events.h" +#include "libc/nt/memory.h" +#include "libc/nt/runtime.h" +#include "libc/nt/signals.h" +#include "libc/nt/struct/memorybasicinformation.h" +#include "libc/nt/struct/ntexceptionpointers.h" +#include "libc/nt/synchronization.h" +#include "libc/nt/thread.h" +#include "libc/runtime/internal.h" +#include "libc/runtime/runtime.h" +#include "libc/runtime/symbols.internal.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/map.h" +#include "libc/sysv/consts/prot.h" +#include "libc/sysv/consts/sa.h" +#include "libc/sysv/consts/sicode.h" +#include "libc/sysv/consts/ss.h" +#include "libc/sysv/consts/termios.h" +#include "libc/thread/posixthread.internal.h" +#ifdef __x86_64__ -struct Signals __sig; +/** + * @fileoverview Cosmopolitan Signals for Windows. + */ -sigset_t __sig_block(void) { - if (IsWindows() || IsMetal()) { - if (__tls_enabled) - return atomic_exchange_explicit(&__get_tls()->tib_sigmask, -1, - memory_order_acquire); - else +#define STKSZ 65536 +#define HAIRY textwindows dontinstrument dontinline + +struct SignalFrame { + unsigned rva; + unsigned flags; + siginfo_t si; + ucontext_t ctx; +}; + +__msabi extern typeof(GetStdHandle) *const __imp_GetStdHandle; +__msabi extern typeof(VirtualProtectEx) *const __imp_VirtualProtectEx; +__msabi extern typeof(VirtualQuery) *const __imp_VirtualQuery; +__msabi extern typeof(WriteFile) *const __imp_WriteFile; + +atomic_int __sig_worker_state; + +textwindows static bool __sig_ignored_by_default(int sig) { + return sig == SIGURG || // + sig == SIGCONT || // + sig == SIGCHLD || // + sig == SIGWINCH; +} + +textwindows bool __sig_ignored(int sig) { + return __sighandrvas[sig] == (intptr_t)SIG_IGN || + (__sighandrvas[sig] == (intptr_t)SIG_DFL && + __sig_ignored_by_default(sig)); +} + +textwindows void __sig_delete(int sig) { + struct Dll *e; + atomic_fetch_and_explicit(__sig.process, ~(1ull << (sig - 1)), + memory_order_relaxed); + _pthread_lock(); + for (e = dll_last(_pthread_list); e; e = dll_prev(_pthread_list, e)) + atomic_fetch_and_explicit(&POSIXTHREAD_CONTAINER(e)->tib->tib_sigpending, + ~(1ull << (sig - 1)), memory_order_relaxed); + _pthread_unlock(); +} + +textwindows static int __sig_getter(atomic_ulong *sigs, sigset_t masked) { + int sig; + sigset_t bit, pending, deliverable; + for (;;) { + pending = atomic_load_explicit(sigs, memory_order_acquire); + if ((deliverable = pending & ~masked)) { + sig = bsfl(deliverable) + 1; + bit = 1ull << (sig - 1); + if (atomic_fetch_and_explicit(sigs, ~bit, memory_order_acq_rel) & bit) + return sig; + } else { return 0; - } else { - sigset_t res, neu = -1; - sys_sigprocmask(SIG_SETMASK, &neu, &res); - return res; + } } } -void __sig_unblock(sigset_t m) { - if (IsWindows() || IsMetal()) { - if (__tls_enabled) { - atomic_store_explicit(&__get_tls()->tib_sigmask, m, memory_order_release); - if (_weaken(__sig_check)) { - _weaken(__sig_check)(); +textwindows int __sig_get(sigset_t masked) { + int sig; + if (!(sig = __sig_getter(&__get_tls()->tib_sigpending, masked))) + sig = __sig_getter(__sig.process, masked); + return sig; +} + +HAIRY static bool __sig_should_use_altstack(unsigned flags, + struct CosmoTib *tib) { + if (!(flags & SA_ONSTACK)) + return false; // signal handler didn't enable it + if (!tib->tib_sigstack_size) + return false; // sigaltstack() wasn't installed on this thread + if (tib->tib_sigstack_flags & SS_DISABLE) + return false; // sigaltstack() on this thread was disabled by user + char *bp = __builtin_frame_address(0); + if (tib->tib_sigstack_addr <= bp && + bp <= tib->tib_sigstack_addr + tib->tib_sigstack_size) + return false; // we're already on the alternate stack + return true; +} + +forceinline wontreturn void __sig_terminate(int sig) { + TerminateThisProcess(sig); +} + +textwindows static bool __sig_wake(struct PosixThread *pt, int sig) { + atomic_int *blocker; + blocker = atomic_load_explicit(&pt->pt_blocker, memory_order_acquire); + if (!blocker) + return false; + // threads can create semaphores on an as-needed basis + if (blocker == PT_BLOCKER_EVENT) { + STRACE("%G set %d's event object", sig, _pthread_tid(pt)); + SetEvent(pt->pt_event); + return !!atomic_load_explicit(&pt->pt_blocker, memory_order_acquire); + } + // all other blocking ops that aren't overlap should use futexes + // we force restartable futexes to churn by waking w/o releasing + STRACE("%G waking %d's futex", sig, _pthread_tid(pt)); + WakeByAddressSingle(blocker); + return !!atomic_load_explicit(&pt->pt_blocker, memory_order_acquire); +} + +textwindows static bool __sig_start(struct PosixThread *pt, int sig, + unsigned *rva, unsigned *flags) { + *rva = __sighandrvas[sig]; + *flags = __sighandflags[sig]; + if (*rva == (intptr_t)SIG_IGN || + (*rva == (intptr_t)SIG_DFL && __sig_ignored_by_default(sig))) { + STRACE("ignoring %G", sig); + return false; + } + if (atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) & + (1ull << (sig - 1))) { + STRACE("enqueing %G on %d", sig, _pthread_tid(pt)); + atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1), + memory_order_relaxed); + __sig_wake(pt, sig); + return false; + } + if (*rva == (intptr_t)SIG_DFL) { + STRACE("terminating on %G due to no handler", sig); + __sig_terminate(sig); + } + return true; +} + +textwindows static sigaction_f __sig_handler(unsigned rva) { + atomic_fetch_add_explicit(&__sig.count, 1, memory_order_relaxed); + return (sigaction_f)(__executable_start + rva); +} + +textwindows int __sig_raise(volatile int sig, int sic) { + + // bitset of kinds of handlers called + volatile int handler_was_called = 0; + + // loop over pending signals + ucontext_t ctx; + getcontext(&ctx); + if (!sig) { + if ((sig = __sig_get(ctx.uc_sigmask))) { + sic = SI_KERNEL; + } else { + return handler_was_called; + } + } + + // process signal(s) + unsigned rva, flags; + struct PosixThread *pt = _pthread_self(); + if (__sig_start(pt, sig, &rva, &flags)) { + + if (flags & SA_RESETHAND) { + STRACE("resetting %G handler", sig); + __sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL; + } + + // update the signal mask in preparation for signal handller + sigset_t blocksigs = __sighandmask[sig]; + if (!(flags & SA_NODEFER)) + blocksigs |= 1ull << (sig - 1); + ctx.uc_sigmask = atomic_fetch_or_explicit(&pt->tib->tib_sigmask, blocksigs, + memory_order_acquire); + + // call the user's signal handler + char ssbuf[128]; + siginfo_t si = {.si_signo = sig, .si_code = sic}; + STRACE("__sig_raise(%G, %t) mask %s", sig, __sig_handler(rva), + _DescribeSigset(ssbuf, 0, (sigset_t *)&pt->tib->tib_sigmask)); + __sig_handler(rva)(sig, &si, &ctx); + + // record this handler + if (flags & SA_RESTART) { + handler_was_called |= SIG_HANDLED_SA_RESTART; + } else { + handler_was_called |= SIG_HANDLED_NO_RESTART; + } + } + + // restore sigmask + // loop back to top + // jump where handler says + sig = 0; + setcontext(&ctx); + __builtin_unreachable(); +} + +textwindows int __sig_relay(int sig, int sic, sigset_t waitmask) { + sigset_t m; + int handler_was_called; + m = atomic_exchange_explicit(&__get_tls()->tib_sigmask, waitmask, + memory_order_acquire); + handler_was_called = __sig_raise(sig, SI_KERNEL); + atomic_store_explicit(&__get_tls()->tib_sigmask, m, memory_order_release); + return handler_was_called; +} + +// the user's signal handler callback is wrapped with this trampoline +textwindows wontreturn static void __sig_tramp(struct SignalFrame *sf) { + int sig = sf->si.si_signo; + struct CosmoTib *tib = __get_tls(); + struct PosixThread *pt = (struct PosixThread *)tib->tib_pthread; + atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release); + for (;;) { + + // update the signal mask in preparation for signal handler + sigset_t blocksigs = __sighandmask[sig]; + if (!(sf->flags & SA_NODEFER)) + blocksigs |= 1ull << (sig - 1); + sf->ctx.uc_sigmask = atomic_fetch_or_explicit(&tib->tib_sigmask, blocksigs, + memory_order_acquire); + + // call the user's signal handler + char ssbuf[2][128]; + STRACE("__sig_tramp(%G, %t) mask %s → %s", sig, __sig_handler(sf->rva), + _DescribeSigset(ssbuf[0], 0, &sf->ctx.uc_sigmask), + _DescribeSigset(ssbuf[1], 0, (sigset_t *)&tib->tib_sigmask)); + __sig_handler(sf->rva)(sig, &sf->si, &sf->ctx); + + // restore the signal mask that was used by the interrupted code + // this may have been modified by the signal handler in the callback + atomic_store_explicit(&tib->tib_sigmask, sf->ctx.uc_sigmask, + memory_order_release); + + // jump back into original code if there aren't any pending signals + do { + if (!(sig = __sig_get(sf->ctx.uc_sigmask))) + __sig_restore(&sf->ctx); + } while (!__sig_start(pt, sig, &sf->rva, &sf->flags)); + + // tail recurse into another signal handler + sf->si.si_signo = sig; + sf->si.si_code = SI_KERNEL; + if (sf->flags & SA_RESETHAND) { + STRACE("resetting %G handler", sig); + __sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL; + } + } +} + +HAIRY optimizespeed void __sig_translate(ucontext_t *ctx, + const struct NtContext *cr) { + ctx->uc_mcontext.eflags = cr->EFlags; + ctx->uc_mcontext.rax = cr->Rax; + ctx->uc_mcontext.rbx = cr->Rbx; + ctx->uc_mcontext.rcx = cr->Rcx; + ctx->uc_mcontext.rdx = cr->Rdx; + ctx->uc_mcontext.rdi = cr->Rdi; + ctx->uc_mcontext.rsi = cr->Rsi; + ctx->uc_mcontext.rbp = cr->Rbp; + ctx->uc_mcontext.rsp = cr->Rsp; + ctx->uc_mcontext.rip = cr->Rip; + ctx->uc_mcontext.r8 = cr->R8; + ctx->uc_mcontext.r9 = cr->R9; + ctx->uc_mcontext.r10 = cr->R10; + ctx->uc_mcontext.r11 = cr->R11; + ctx->uc_mcontext.r12 = cr->R12; + ctx->uc_mcontext.r13 = cr->R13; + ctx->uc_mcontext.r14 = cr->R14; + ctx->uc_mcontext.r15 = cr->R15; + ctx->uc_mcontext.cs = cr->SegCs; + ctx->uc_mcontext.gs = cr->SegGs; + ctx->uc_mcontext.fs = cr->SegFs; + ctx->uc_mcontext.fpregs = &ctx->__fpustate; + __repmovsb(&ctx->__fpustate, &cr->FltSave, sizeof(ctx->__fpustate)); + ctx->__fpustate.mxcsr = cr->MxCsr; +} + +// sends signal to another specific thread which is ref'd +textwindows static int __sig_killer(struct PosixThread *pt, int sig, int sic) { + unsigned rva = __sighandrvas[sig]; + unsigned flags = __sighandflags[sig]; + + // do nothing if signal is ignored + if (rva == (intptr_t)SIG_IGN || + (rva == (intptr_t)SIG_DFL && __sig_ignored_by_default(sig))) { + STRACE("ignoring %G", sig); + return 0; + } + + // we can't preempt threads that masked sigs or are blocked on i/o + while ((atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) & + (1ull << (sig - 1)))) { + if (atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1), + memory_order_acq_rel) & + (1ull << (sig - 1))) + // we believe signal was already enqueued + return 0; + if (__sig_wake(pt, sig)) + // we believe i/o routine will handle signal + return 0; + if (atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) & + (1ull << (sig - 1))) + // we believe ALLOW_SIGNALS will handle signal + return 0; + if (!(atomic_fetch_and_explicit(&pt->tib->tib_sigpending, + ~(1ull << (sig - 1)), + memory_order_acq_rel) & + (1ull << (sig - 1)))) + // we believe another thread sniped our signal + return 0; + break; + } + + // avoid race conditions and deadlocks with thread suspend process + if (atomic_exchange_explicit(&pt->pt_intoff, 1, memory_order_acquire)) { + // we believe another thread is asynchronously waking the mark + if (atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1), + memory_order_acq_rel) & + (1ull << (sig - 1))) + // we believe our signal is already being delivered + return 0; + if (atomic_load_explicit(&pt->pt_intoff, memory_order_acquire) || + atomic_exchange_explicit(&pt->pt_intoff, 1, memory_order_acquire)) + // we believe __sig_tramp will deliver our signal + return 0; + if (!(atomic_fetch_and_explicit(&pt->tib->tib_sigpending, + ~(1ull << (sig - 1)), + memory_order_acq_rel) & + (1ull << (sig - 1)))) + // we believe another thread sniped our signal + return 0; + } + + // if there's no handler then killing a thread kills the process + if (rva == (intptr_t)SIG_DFL) { + STRACE("terminating on %G due to no handler", sig); + __sig_terminate(sig); + } + + // take control of thread + // suspending the thread happens asynchronously + // however getting the context blocks until it's frozen + uintptr_t th = _pthread_syshand(pt); + if (SuspendThread(th) == -1u) { + STRACE("SuspendThread failed w/ %d", GetLastError()); + atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release); + return ESRCH; + } + struct NtContext nc; + nc.ContextFlags = kNtContextFull; + if (!GetThreadContext(th, &nc)) { + STRACE("GetThreadContext failed w/ %d", GetLastError()); + ResumeThread(th); + atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release); + return ESRCH; + } + + // we can't preempt threads that masked sig or are blocked + // we can't preempt threads that are running in win32 code + // so we shall unblock the thread and let it signal itself + if (!((uintptr_t)__executable_start <= nc.Rip && + nc.Rip < (uintptr_t)__privileged_start)) { + atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1), + memory_order_relaxed); + ResumeThread(th); + atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release); + __sig_wake(pt, sig); + return 0; + } + + // preferring to live dangerously + // the thread will be signaled asynchronously + if (flags & SA_RESETHAND) { + STRACE("resetting %G handler", sig); + __sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL; + } + + // inject call to trampoline function into thread + uintptr_t sp; + if (__sig_should_use_altstack(flags, pt->tib)) { + sp = (uintptr_t)pt->tib->tib_sigstack_addr + pt->tib->tib_sigstack_size; + } else { + sp = nc.Rsp; + } + sp -= sizeof(struct SignalFrame); + sp &= -16; + struct SignalFrame *sf = (struct SignalFrame *)sp; + __repstosb(sf, 0, sizeof(*sf)); + __sig_translate(&sf->ctx, &nc); + sf->rva = rva; + sf->flags = flags; + sf->si.si_code = sic; + sf->si.si_signo = sig; + *(uintptr_t *)(sp -= sizeof(uintptr_t)) = nc.Rip; + nc.Rip = (intptr_t)__sig_tramp; + nc.Rdi = (intptr_t)sf; + nc.Rsp = sp; + if (!SetThreadContext(th, &nc)) { + STRACE("SetThreadContext failed w/ %d", GetLastError()); + atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release); + return ESRCH; + } + ResumeThread(th); + __sig_wake(pt, sig); + return 0; +} + +// sends signal to another specific thread +textwindows int __sig_kill(struct PosixThread *pt, int sig, int sic) { + int rc; + BLOCK_SIGNALS; + rc = __sig_killer(pt, sig, sic); + ALLOW_SIGNALS; + return rc; +} + +// sends signal to any other thread +// this should only be called by non-posix threads +textwindows void __sig_generate(int sig, int sic) { + struct Dll *e; + struct PosixThread *pt, *mark = 0; + if (__sig_ignored(sig)) { + STRACE("ignoring %G", sig); + return; + } + if (__sighandrvas[sig] == (intptr_t)SIG_DFL) { + STRACE("terminating on %G due to no handler", sig); + __sig_terminate(sig); + } + if (atomic_load_explicit(__sig.process, memory_order_acquire) & + (1ull << (sig - 1))) + return; + _pthread_lock(); + for (e = dll_first(_pthread_list); e; e = dll_next(_pthread_list, e)) { + pt = POSIXTHREAD_CONTAINER(e); + // we don't want to signal ourself + if (pt == _pthread_self()) + continue; + // we don't want to signal a thread that isn't running + if (atomic_load_explicit(&pt->pt_status, memory_order_acquire) >= + kPosixThreadTerminated) + continue; + // choose this thread if it isn't masking sig + if (!(atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) & + (1ull << (sig - 1)))) { + _pthread_ref(pt); + mark = pt; + break; + } + // if a thread is blocking then we check to see if it's planning + // to unblock our sig once the wait operation is completed; when + // that's the case we can cancel the thread's i/o to deliver sig + if (atomic_load_explicit(&pt->pt_blocker, memory_order_acquire) && + !(pt->pt_blkmask & (1ull << (sig - 1)))) { + _pthread_ref(pt); + mark = pt; + break; + } + } + _pthread_unlock(); + if (mark) { + // no lock needed since current thread is nameless and formless + __sig_killer(mark, sig, sic); + _pthread_unref(mark); + } else { + atomic_fetch_or_explicit(__sig.process, 1ull << (sig - 1), + memory_order_relaxed); + } +} + +textwindows static char *__sig_stpcpy(char *d, const char *s) { + size_t i; + for (i = 0;; ++i) + if (!(d[i] = s[i])) + return d + i; +} + +textwindows wontreturn static void __sig_death(int sig, const char *thing) { +#ifndef TINY + intptr_t hStderr; + char sigbuf[21], s[128], *p; + hStderr = __imp_GetStdHandle(kNtStdErrorHandle); + p = __sig_stpcpy(s, "Terminating on "); + p = __sig_stpcpy(p, thing); + p = __sig_stpcpy(p, strsignal_r(sig, sigbuf)); + p = __sig_stpcpy(p, + ". Pass --strace and/or ShowCrashReports() for details.\n"); + __imp_WriteFile(hStderr, s, p - s, 0, 0); +#endif + __sig_terminate(sig); +} + +// +// "If a program attempts to access an address within a guard page, +// the system raises a kNtStatusGuardPageViolation (0x80000001) +// exception. The system also clears the kNtPageGuard modifier, +// removing the memory page's guard page status. The system will not +// stop the next attempt to access the memory page with a +// kNtStatusGuardPageViolation exception." +// +// —Quoth MSDN § Creating Guard Pages +// +forceinline void __sig_reguard(void *page) { + uint32_t old_protect; + __imp_VirtualProtectEx(GetCurrentProcess(), + (void *)((uintptr_t)page & -__pagesize), __pagesize, + kNtPageReadwrite | kNtPageGuard, &old_protect); +} + +// trampoline for calling signal handler when system reports crash +textwindows static void __sig_unmaskable(struct SignalFrame *sf) { + + // log vital crash information reliably for --strace before doing much + // we don't print this without the flag since raw numbers scare people + // this needs at least one page of stack memory in order to get logged + // otherwise it'll print a warning message about the lack of stack mem + STRACE("win32 vectored exception 0x%08Xu raising %G " + "cosmoaddr2line %s %lx %s", + sf->si.si_errno, sf->si.si_signo, + _weaken(FindDebugBinary) ? _weaken(FindDebugBinary)() + : program_invocation_name, + sf->ctx.uc_mcontext.gregs[REG_RIP], + DescribeBacktrace( + (struct StackFrame *)sf->ctx.uc_mcontext.gregs[REG_RBP])); + + // kills process if the user did not specify a handler for this signal + // we also don't allow unmaskable signals to be ignored by the program + if (sf->rva == (intptr_t)SIG_DFL || // + sf->rva == (intptr_t)SIG_IGN) + __sig_death(sf->si.si_signo, "uncaught "); + + // we kill the process if this thread's signal mask blocks this signal + // then we block some extra signals while executing the signal handler + struct CosmoTib *tib = __get_tls(); + sigset_t blocksigs = __sighandmask[sf->si.si_signo]; + if (!(sf->flags & SA_NODEFER)) + blocksigs |= 1ull << (sf->si.si_signo - 1); + sf->ctx.uc_sigmask = atomic_fetch_or(&tib->tib_sigmask, blocksigs); + if (sf->ctx.uc_sigmask & (1ull << (sf->si.si_signo - 1))) + __sig_death(sf->si.si_signo, "masked "); + + // this will restore the guard page if the user is using a sigaltstack + if (sf->si.si_errno == kNtStatusGuardPageViolation) + __sig_reguard(sf->si.si_addr); + + // call the user signal handler + // and a modifiable view of the faulting code's cpu state + // then finally restore signal mask and return control to program + __sig_handler(sf->rva)(sf->si.si_signo, &sf->si, &sf->ctx); + atomic_store_explicit(&__get_tls()->tib_sigmask, sf->ctx.uc_sigmask, + memory_order_release); + setcontext(&sf->ctx); + __builtin_unreachable(); +} + +// abashed the devil stood +// and felt how awful goodness is +__msabi HAIRY static unsigned __sig_crash(struct NtExceptionPointers *ep) { + + // translate the win32 exception code into unix's si_signo and si_code + int sic, sig = __sig_crash_sig(ep->ExceptionRecord->ExceptionCode, &sic); + + // advances the instruction pointer, to skip over debugger breakpoints + // this makes windows consistent with how unix kernels are implemented + if (sig == SIGTRAP) + ep->ContextRecord->Rip++; + + // clears signal handler if user asked sigaction for one-shot behavior + unsigned rva = __sighandrvas[sig]; + unsigned flags = __sighandflags[sig]; + if (flags & SA_RESETHAND) + __sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL; + + // we don't know if it is safe for signal handlers to longjmp() out of + // win32 vectored exception handlers so let's copy the machine context + // and tell win32 to restore control to __sig_unmaskable() which shall + // call the user signal handler safely. please note that if this crash + // was caused by stack overflow, then we're literally executing inside + // the guard page so this code can't use more than 4096 bytes of stack + uintptr_t sp; + struct CosmoTib *tib = __get_tls(); + if (__sig_should_use_altstack(flags, tib)) { + sp = (uintptr_t)tib->tib_sigstack_addr + tib->tib_sigstack_size; + } else { + size_t n = sizeof(struct SignalFrame) + 32; + sp = (uintptr_t)alloca(n) + n; + } + sp -= sizeof(struct SignalFrame); + sp &= -16; + struct SignalFrame *sf = (struct SignalFrame *)sp; + __repstosb(sf, 0, sizeof(*sf)); + __sig_translate(&sf->ctx, ep->ContextRecord); + sf->rva = rva; + sf->flags = flags; + sf->si.si_code = sic; + sf->si.si_signo = sig; + sf->si.si_errno = ep->ExceptionRecord->ExceptionCode; + if (sf->si.si_errno == kNtStatusGuardPageViolation) { + sf->si.si_addr = (void *)ep->ExceptionRecord->ExceptionInformation[1]; + } else { + sf->si.si_addr = ep->ExceptionRecord->ExceptionAddress; + } + *(uintptr_t *)(sp -= sizeof(uintptr_t)) = ep->ContextRecord->Rip; + ep->ContextRecord->Rip = (intptr_t)__sig_unmaskable; + ep->ContextRecord->Rdi = (intptr_t)sf; + ep->ContextRecord->Rsp = sp; + return kNtExceptionContinueExecution; +} + +textwindows static int __sig_console_sig(uint32_t dwCtrlType) { + switch (dwCtrlType) { + case kNtCtrlCEvent: + return SIGINT; + case kNtCtrlBreakEvent: + return SIGQUIT; + case kNtCtrlCloseEvent: + case kNtCtrlLogoffEvent: // only received by services + case kNtCtrlShutdownEvent: // only received by services + return SIGHUP; + default: + return SIGSTKFLT; + } +} + +textwindows static int __sig_console_char(uint32_t dwCtrlType) { + switch (dwCtrlType) { + case kNtCtrlCEvent: + return __ttyconf.vintr; + case kNtCtrlBreakEvent: + return __ttyconf.vquit; + default: + return _POSIX_VDISABLE; + } +} + +__msabi HAIRY bool32 __sig_console(uint32_t dwCtrlType) { + // win32 launches a thread to deliver ctrl-c and ctrl-break when typed + // it only happens when kNtEnableProcessedInput is in play on console. + // otherwise we need to wait until read-nt.c discovers that keystroke. + struct CosmoTib tls; + __bootstrap_tls(&tls, __builtin_frame_address(0)); + + // ensure that ^C or ^\ gets printed to console appropriately + if (_weaken(EchoConsoleNt)) { + char c; + if ((c = __sig_console_char(dwCtrlType)) != _POSIX_VDISABLE) + _weaken(EchoConsoleNt)(&c, sizeof(c), false); + } + + // take control of random thread and inject call to signal handler + __sig_generate(__sig_console_sig(dwCtrlType), SI_KERNEL); + return true; +} + +// returns 0 if no signal handlers were called, otherwise a bitmask +// consisting of `1` which means a signal handler was invoked which +// didn't have the SA_RESTART flag, and `2`, which means SA_RESTART +// handlers were called (or `3` if both were the case). +textwindows int __sig_check(void) { + int sig, res = 0; + while ((sig = __sig_get(atomic_load_explicit(&__get_tls()->tib_sigmask, + memory_order_acquire)))) + res |= __sig_raise(sig, SI_KERNEL); + return res; +} + +// background thread for delivering inter-process signals asynchronously +// this checks for undelivered process-wide signals, once per scheduling +// quantum, which on windows should be every ~15ms or so, unless somehow +// the process was tuned to have more fine-grained event timing. we want +// signals to happen faster when possible; that happens when cancelation +// points, e.g. read need to wait on i/o; they too check for new signals +HAIRY static uint32_t __sig_worker(void *arg) { + struct CosmoTib tls; + __bootstrap_tls(&tls, __builtin_frame_address(0)); + char *sp = __builtin_frame_address(0); + __maps_track((char *)(((uintptr_t)sp + __pagesize - 1) & -__pagesize) - STKSZ, + STKSZ, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_NOFORK); + for (;;) { + // ok sys_execve_nt() might disable this worker + if (~__sig_worker_state & 2) { + + // dequeue all pending signals and fire them off. if there's no + // thread that can handle them then __sig_generate will requeue + // those signals back to __sig.process; hence the need for xchg + unsigned long sigs = + atomic_exchange_explicit(__sig.process, 0, memory_order_acq_rel); + while (sigs) { + int sig = bsfl(sigs) + 1; + sigs &= ~(1ull << (sig - 1)); + __sig_generate(sig, SI_KERNEL); + } + + // unblock stalled i/o signals in threads + _pthread_lock(); + for (struct Dll *e = dll_first(_pthread_list); e; + e = dll_next(_pthread_list, e)) { + struct PosixThread *pt = POSIXTHREAD_CONTAINER(e); + if (atomic_load_explicit(&pt->pt_status, memory_order_acquire) >= + kPosixThreadTerminated) + break; + if (atomic_load_explicit(&pt->pt_blocker, memory_order_acquire) && + (atomic_load_explicit(&pt->tib->tib_sigpending, + memory_order_acquire) & + ~atomic_load_explicit(&pt->pt_blkmask, memory_order_acquire))) + __sig_wake(pt, 0); + } + _pthread_unlock(); + + // unblock stalled asynchronous signals in threads + for (;;) { + sigset_t pending, mask; + struct PosixThread *mark = 0; + _pthread_lock(); + for (struct Dll *e = dll_first(_pthread_list); e; + e = dll_next(_pthread_list, e)) { + struct PosixThread *pt = POSIXTHREAD_CONTAINER(e); + if (atomic_load_explicit(&pt->pt_status, memory_order_acquire) >= + kPosixThreadTerminated) + break; + pending = atomic_load_explicit(&pt->tib->tib_sigpending, + memory_order_acquire); + mask = + atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire); + if (pending & ~mask) { + _pthread_ref(pt); + mark = pt; + break; + } + } + _pthread_unlock(); + if (!mark) + break; + while (!atomic_compare_exchange_weak_explicit( + &mark->tib->tib_sigpending, &pending, pending & ~mask, + memory_order_acq_rel, memory_order_relaxed)) { + } + while ((pending = pending & ~mask)) { + int sig = bsfl(pending) + 1; + pending &= ~(1ull << (sig - 1)); + __sig_killer(mark, sig, SI_KERNEL); + } + _pthread_unref(mark); } } - } else { - sys_sigprocmask(SIG_SETMASK, &m, 0); + + // wait until next scheduler quantum + __sig_worker_state |= 1; + Sleep(POLL_INTERVAL_MS); + __sig_worker_state &= ~1; } + __builtin_unreachable(); } + +__attribute__((__constructor__(10))) textstartup void __sig_init(void) { + if (!IsWindows()) + return; + AddVectoredExceptionHandler(true, (void *)__sig_crash); + SetConsoleCtrlHandler((void *)__sig_console, true); + CreateThread(0, STKSZ, __sig_worker, 0, kNtStackSizeParamIsAReservation, 0); +} + +#endif /* __x86_64__ */ diff --git a/libc/intrin/sigblock.c b/libc/intrin/sigblock.c new file mode 100644 index 000000000..919dced56 --- /dev/null +++ b/libc/intrin/sigblock.c @@ -0,0 +1,57 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2023 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/sig.internal.h" +#include "libc/calls/struct/sigset.internal.h" +#include "libc/dce.h" +#include "libc/intrin/atomic.h" +#include "libc/intrin/weaken.h" +#include "libc/sysv/consts/sig.h" +#include "libc/thread/tls.h" + +// since there's so many c library interfaces and system call wrappers +// that always need to block signals we avoid the distraction of their +// ftrace and strace output being muddied with sigprocmask lines. it's +// usually better that sigprocmask only strace the user is calling it. +// plus, since we have a very specific use case, this code goes faster + +sigset_t __sig_block(void) { + if (IsWindows() || IsMetal()) { + if (__tls_enabled) + return atomic_exchange_explicit(&__get_tls()->tib_sigmask, -1, + memory_order_acquire); + else + return 0; + } else { + sigset_t res, neu = -1; + sys_sigprocmask(SIG_SETMASK, &neu, &res); + return res; + } +} + +void __sig_unblock(sigset_t m) { + if (IsWindows() || IsMetal()) { + if (__tls_enabled) { + atomic_store_explicit(&__get_tls()->tib_sigmask, m, memory_order_release); + if (_weaken(__sig_check)) + _weaken(__sig_check)(); + } + } else { + sys_sigprocmask(SIG_SETMASK, &m, 0); + } +} diff --git a/libc/calls/sigcrashsig.c b/libc/intrin/sigcrashsig.c similarity index 100% rename from libc/calls/sigcrashsig.c rename to libc/intrin/sigcrashsig.c diff --git a/libc/intrin/sigproc.c b/libc/intrin/sigproc.c new file mode 100644 index 000000000..e3f6d0673 --- /dev/null +++ b/libc/intrin/sigproc.c @@ -0,0 +1,126 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 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/atomic.h" +#include "libc/calls/sig.internal.h" +#include "libc/fmt/internal.h" +#include "libc/nt/createfile.h" +#include "libc/nt/enum/accessmask.h" +#include "libc/nt/enum/creationdisposition.h" +#include "libc/nt/enum/fileflagandattributes.h" +#include "libc/nt/enum/filemapflags.h" +#include "libc/nt/enum/filemovemethod.h" +#include "libc/nt/enum/filesharemode.h" +#include "libc/nt/enum/pageflags.h" +#include "libc/nt/files.h" +#include "libc/nt/memory.h" +#include "libc/nt/process.h" +#include "libc/nt/runtime.h" +#include "libc/nt/thunk/msabi.h" +#ifdef __x86_64__ + +#define ABI __msabi textwindows dontinstrument + +// cut back on code size and avoid setting errno +// this code is a mandatory dependency of winmain +__msabi extern typeof(CloseHandle) *const __imp_CloseHandle; +__msabi extern typeof(CreateDirectory) *const __imp_CreateDirectoryW; +__msabi extern typeof(CreateFile) *const __imp_CreateFileW; +__msabi extern typeof(CreateFileMapping) *const __imp_CreateFileMappingW; +__msabi extern typeof(MapViewOfFileEx) *const __imp_MapViewOfFileEx; +__msabi extern typeof(SetEndOfFile) *const __imp_SetEndOfFile; +__msabi extern typeof(SetFilePointer) *const __imp_SetFilePointer; +__msabi extern typeof(GetEnvironmentVariable) + *const __imp_GetEnvironmentVariableW; + +// Generates C:\ProgramData\cosmo\sig\x\y.pid like path +ABI char16_t *__sig_process_path(char16_t *path, uint32_t pid, + int create_directories) { + char16_t buf[3]; + char16_t *p = path; + uint32_t vlen = __imp_GetEnvironmentVariableW(u"SYSTEMDRIVE", buf, 3); + *p++ = vlen == 2 ? buf[0] : 'C'; + *p++ = ':'; + *p++ = '\\'; + *p++ = 'P'; + *p++ = 'r'; + *p++ = 'o'; + *p++ = 'g'; + *p++ = 'r'; + *p++ = 'a'; + *p++ = 'm'; + *p++ = 'D'; + *p++ = 'a'; + *p++ = 't'; + *p++ = 'a'; + *p = 0; + if (create_directories) + __imp_CreateDirectoryW(path, 0); + *p++ = '\\'; + *p++ = 'c'; + *p++ = 'o'; + *p++ = 's'; + *p++ = 'm'; + *p++ = 'o'; + *p = 0; + if (create_directories) + __imp_CreateDirectoryW(path, 0); + *p++ = '\\'; + *p++ = 's'; + *p++ = 'i'; + *p++ = 'g'; + *p = 0; + if (create_directories) + __imp_CreateDirectoryW(path, 0); + *p++ = '\\'; + p = __itoa16(p, (pid & 0x000ff800) >> 11); + *p = 0; + if (create_directories) + __imp_CreateDirectoryW(path, 0); + *p++ = '\\'; + p = __itoa16(p, pid); + *p++ = '.'; + *p++ = 'p'; + *p++ = 'i'; + *p++ = 'd'; + *p = 0; + return path; +} + +ABI atomic_ulong *__sig_map_process(int pid, int disposition) { + char16_t path[128]; + __sig_process_path(path, pid, disposition == kNtOpenAlways); + intptr_t hand = __imp_CreateFileW(path, kNtGenericRead | kNtGenericWrite, + kNtFileShareRead | kNtFileShareWrite, 0, + disposition, kNtFileAttributeNormal, 0); + if (hand == -1) + return 0; + __imp_SetFilePointer(hand, 8, 0, kNtFileBegin); + __imp_SetEndOfFile(hand); + intptr_t map = __imp_CreateFileMappingW(hand, 0, kNtPageReadwrite, 0, 8, 0); + if (!map) { + __imp_CloseHandle(hand); + return 0; + } + atomic_ulong *sigs = __imp_MapViewOfFileEx(map, kNtFileMapWrite, 0, 0, 8, 0); + __imp_CloseHandle(map); + __imp_CloseHandle(hand); + return sigs; +} + +#endif /* __x86_64__ */ diff --git a/libc/intrin/sigprocmask-nt.c b/libc/intrin/sigprocmask-nt.c index 0d31b61af..38246b430 100644 --- a/libc/intrin/sigprocmask-nt.c +++ b/libc/intrin/sigprocmask-nt.c @@ -16,7 +16,6 @@ │ 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/intrin/atomic.h" @@ -27,38 +26,25 @@ #ifdef __x86_64__ textwindows int __sig_mask(int how, const sigset_t *neu, sigset_t *old) { - - // validate api usage - if (how != SIG_BLOCK && how != SIG_UNBLOCK && how != SIG_SETMASK) { + if (how != SIG_BLOCK && how != SIG_UNBLOCK && how != SIG_SETMASK) return einval(); - } - - // get address of sigset to modify - _Atomic(uint64_t) *mask = &__get_tls()->tib_sigmask; - - // handle read-only case sigset_t oldmask; + atomic_ulong *mask = &__get_tls()->tib_sigmask; if (neu) { if (how == SIG_BLOCK) { - oldmask = atomic_fetch_or_explicit(mask, *neu, memory_order_acq_rel); + oldmask = atomic_fetch_or(mask, *neu); } else if (how == SIG_UNBLOCK) { - oldmask = atomic_fetch_and_explicit(mask, ~*neu, memory_order_acq_rel); - } else { // SIG_SETMASK - oldmask = atomic_exchange_explicit(mask, *neu, memory_order_acq_rel); + oldmask = atomic_fetch_and(mask, ~*neu); + } else { + oldmask = atomic_exchange(mask, *neu); } + if (_weaken(__sig_check)) + _weaken(__sig_check)(); } else { - oldmask = atomic_load_explicit(mask, memory_order_acquire); + oldmask = atomic_load(mask); } - - // return old signal mask to caller - if (old) { + if (old) *old = oldmask; - } - - if (_weaken(__sig_check)) { - _weaken(__sig_check)(); - } - return 0; } diff --git a/libc/intrin/sigprocmask-sysv.c b/libc/intrin/sigprocmask-sysv.c index 685f36c15..c7a43aaeb 100644 --- a/libc/intrin/sigprocmask-sysv.c +++ b/libc/intrin/sigprocmask-sysv.c @@ -32,8 +32,7 @@ int sys_sigprocmask(int how, const sigset_t *opt_set, how, opt_set ? (sigset_t *)(intptr_t)(uint32_t)*opt_set : 0, 0, 0); rc = 0; } - if (rc != -1 && opt_out_oldset) { + if (rc != -1 && opt_out_oldset) *opt_out_oldset = old[0]; - } return rc; } diff --git a/libc/intrin/sigprocmask.c b/libc/intrin/sigprocmask.c index aa76966ab..bb1406624 100644 --- a/libc/intrin/sigprocmask.c +++ b/libc/intrin/sigprocmask.c @@ -16,18 +16,12 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/calls.h" -#include "libc/calls/internal.h" #include "libc/calls/sig.internal.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/struct/sigset.internal.h" #include "libc/dce.h" -#include "libc/fmt/itoa.h" #include "libc/intrin/describeflags.h" #include "libc/intrin/strace.h" -#include "libc/str/str.h" -#include "libc/sysv/consts/sig.h" -#include "libc/sysv/errfuns.h" /** * Changes signal blocking state of calling thread, e.g.: @@ -55,9 +49,8 @@ int sigprocmask(int how, const sigset_t *opt_set, sigset_t *opt_out_oldset) { } else { rc = sys_sigprocmask(how, opt_set, opt_out_oldset ? &old : 0); } - if (rc != -1 && opt_out_oldset) { + if (rc != -1 && opt_out_oldset) *opt_out_oldset = old; - } STRACE("sigprocmask(%s, %s, [%s]) → %d% m", DescribeHow(how), DescribeSigset(0, opt_set), DescribeSigset(rc, opt_out_oldset), rc); return rc; diff --git a/libc/stdio/g_rando.c b/libc/intrin/sigvar.c similarity index 93% rename from libc/stdio/g_rando.c rename to libc/intrin/sigvar.c index c702b2fd8..21c1d2945 100644 --- a/libc/stdio/g_rando.c +++ b/libc/intrin/sigvar.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2023 Justine Alexandra Roberts Tunney │ +│ Copyright 2024 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,6 +16,6 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/stdio/stdio.h" +#include "libc/calls/sig.internal.h" -uint64_t g_rando = 1; +struct Signals __sig; diff --git a/libc/intrin/stack.c b/libc/intrin/stack.c new file mode 100644 index 000000000..27a20a06c --- /dev/null +++ b/libc/intrin/stack.c @@ -0,0 +1,513 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/stack.h" +#include "libc/assert.h" +#include "libc/atomic.h" +#include "libc/calls/calls.h" +#include "libc/calls/syscall-sysv.internal.h" +#include "libc/cosmo.h" +#include "libc/dce.h" +#include "libc/dlopen/dlfcn.h" +#include "libc/errno.h" +#include "libc/intrin/describeflags.h" +#include "libc/intrin/dll.h" +#include "libc/intrin/maps.h" +#include "libc/intrin/rlimit.h" +#include "libc/intrin/strace.h" +#include "libc/intrin/weaken.h" +#include "libc/limits.h" +#include "libc/macros.h" +#include "libc/runtime/runtime.h" +#include "libc/sock/internal.h" +#include "libc/sysv/consts/map.h" +#include "libc/sysv/consts/prot.h" +#include "libc/thread/posixthread.internal.h" +#include "libc/thread/thread.h" + +/** + * @fileoverview cosmo stack memory manager + */ + +#define MAP_GROWSDOWN_LINUX 0x00000100 +#define MAP_ANONYMOUS_LINUX 0x00000020 +#define MAP_NOREPLACE_LINUX 0x08000000 +#define MAP_NORESERVE_LINUX 0x00004000 + +#define MAP_ANON_OPENBSD 0x1000 +#define MAP_STACK_OPENBSD 0x4000 + +#define THREADSTACK_CONTAINER(e) DLL_CONTAINER(struct CosmoStack, elem, e) + +struct CosmoStack { + struct Dll elem; + void *stackaddr; + size_t stacksize; + size_t guardsize; +}; + +struct CosmoStacks { + atomic_uint once; + pthread_mutex_t lock; + struct Dll *stacks; + struct Dll *objects; + unsigned count; +}; + +struct CosmoStacksConfig { + unsigned maxstacks; +}; + +static struct CosmoStacks cosmo_stacks = { + .lock = PTHREAD_MUTEX_INITIALIZER, +}; + +static struct CosmoStacksConfig cosmo_stacks_config = { + .maxstacks = 3, +}; + +void cosmo_stack_lock(void) { + _pthread_mutex_lock(&cosmo_stacks.lock); +} + +void cosmo_stack_unlock(void) { + _pthread_mutex_unlock(&cosmo_stacks.lock); +} + +void cosmo_stack_wipe(void) { + _pthread_mutex_wipe_np(&cosmo_stacks.lock); +} + +// map_growsdown will not grow more than rlimit_stack +static size_t cosmo_stack_maxgrow(void) { + return __rlimit_stack_get().rlim_cur & -__pagesize; +} + +// allocates private anonymous fixed noreplace memory on linux +static void *flixmap(void *addr, size_t size, int prot, int flags) { + flags |= MAP_PRIVATE | MAP_ANONYMOUS_LINUX | MAP_NOREPLACE_LINUX; + void *res = __sys_mmap(addr, size, prot, flags, -1, 0, 0); + if (res != MAP_FAILED) { + if (res != addr) { + sys_munmap(addr, size); + errno = EEXIST; // polyfill linux 4.17+ behavior + res = 0; + } + } else { + res = 0; + } + STRACE("mmap(%p, %'zu, %s, %s) → %p% m", addr, size, DescribeProtFlags(prot), + DescribeMapFlags(flags), res); + return res; +} + +// maps stack on linux +static void *slackmap(size_t stacksize, size_t guardsize) { + int olde = errno; + struct Map *prev, *map; + char *max = (char *)PTRDIFF_MAX; + size_t need = guardsize + stacksize; + __maps_lock(); + for (;;) { + + // look for empty space beneath higher mappings + char *region = 0; + for (map = __maps_floor(max); map; map = prev) { + char *min = (char *)(intptr_t)__gransize; + if ((prev = __maps_prev(map))) + min = prev->addr + ROUNDUP(prev->size, __gransize); + if (map->addr - min >= need) { + region = map->addr - need; + max = region - 1; + break; + } + } + if (!region) + break; + + // track intended memory in rbtree + if (!__maps_track(region, guardsize, PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS_LINUX)) + break; + if (!__maps_track(region + guardsize, stacksize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS_LINUX)) { + __maps_untrack(region, need); + break; + } + __maps_unlock(); + + // ask kernel to create guard region + // taking special care to not clobber untracked mappings + // + // it's important that this call happen first, since it limits how + // much memory map_growsdown will secretly consume. if there's + // nothing beneath a map_growsdown mapping, then the kernel reserves + // (and this isn't listed /proc/PID/maps so don't bother looking) + // `rlimit_stack.rlim_cur & -__pagesize` bytes of memory including + // this top-most page, and another 1mb of guard pages beneath that. + // but by mapping our guard pages manually, we ensure the guard + // region and the stack itself will be exactly as big as we want. + // + // you'd think we could mmap(0, pagesz, growsdown) to let the kernel + // pick an address and then we could just upscale the user's stack + // size request to whatever rlimit_stack is if it's bigger. but the + // linux kernel will actually choose addresses between existing maps + // where the hole is smaller than rlimit_stack. + // + // to use map_growsdown, we must use map_fixed. normally when we use + // map_fixed, we reserve an entire kernel-assigned region beforehand + // to ensure there isn't any overlap with existing mappings. however + // since growsdown stops growing when it encounters another mapping, + // you can't map it on top of a reservation mapping. so we must take + // a leap of faith there aren't any mystery mappings twixt the guard + // region and growsdown page below. + char *guard_region = + flixmap(region, guardsize, PROT_NONE, MAP_NORESERVE_LINUX); + if (!guard_region) { + RecoverFromMmapFailure: + if (errno != EEXIST) { + // mmap() probably raised enomem due to rlimit_as etc. + __maps_untrack(region, need); + return 0; + } else { + // we've encountered a mystery mapping. it's hard to imagine + // this happening, since we don't use map_growsdown when + // cosmo_dlopen() is linked in the binary. in that case, the + // tracker we created covers at least some of the rogue map, + // therefore this issue should fix itself if we keep going + errno = olde; + __maps_lock(); + ++max; + continue; + } + } + + // ask kernel to create stack pages + // taking special care to not clobber untracked mappings + char *top_page = flixmap(region + need - __pagesize, __pagesize, + PROT_READ | PROT_WRITE, MAP_GROWSDOWN_LINUX); + if (!top_page) { + sys_munmap(region, guardsize); + goto RecoverFromMmapFailure; + } + + // return address to bottom of stack + return region + guardsize; + } + __maps_unlock(); + errno = ENOMEM; + return 0; +} + +static errno_t cosmo_stack_munmap(char *stackaddr, size_t stacksize, + size_t guardsize) { + errno_t r = 0; + errno_t e = errno; + if (!munmap(stackaddr - guardsize, // + guardsize + stacksize)) { + r = errno; + errno = e; + } + return r; +} + +static void cosmo_stack_populate(void) { + errno_t e = errno; + void *map = mmap(0, __pagesize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + errno = e; + if (map != MAP_FAILED) { + struct CosmoStack *ts = map; + int n = __pagesize / sizeof(struct CosmoStack); + for (int i = 0; i < n; ++i) { + dll_init(&ts[i].elem); + dll_make_first(&cosmo_stacks.objects, &ts[i].elem); + } + } +} + +static struct Dll *cosmo_stack_decimate(unsigned maxstacks) { + struct Dll *surplus = 0; + while (cosmo_stacks.count > maxstacks) { + struct Dll *e = dll_last(cosmo_stacks.stacks); + dll_remove(&cosmo_stacks.stacks, e); + dll_make_first(&surplus, e); + --cosmo_stacks.count; + } + return surplus; +} + +static void cosmo_stack_rehabilitate(struct Dll *stacks) { + struct Dll *e; + for (e = dll_first(stacks); e; e = dll_next(stacks, e)) + cosmo_stack_munmap(THREADSTACK_CONTAINER(e)->stackaddr, + THREADSTACK_CONTAINER(e)->stacksize, + THREADSTACK_CONTAINER(e)->guardsize); + cosmo_stack_lock(); + dll_make_first(&cosmo_stacks.objects, stacks); + cosmo_stack_unlock(); +} + +/** + * Empties unused stack cache. + * + * To make POSIX threads as cheap as possible to spawn, we recycle their + * stacks without zeroing their memory. On Linux for an 80kb stack size, + * that makes launching a thread take 40µs rather than 80µs. However the + * stack cache needs to be cleared in certain cases. This is called upon + * exit() automatically but anyone can clear this at any other time too. + * + * @see pthread_decimate_np() + */ +void cosmo_stack_clear(void) { + cosmo_stack_lock(); + struct Dll *stacks = cosmo_stacks.stacks; + cosmo_stacks.stacks = 0; + cosmo_stacks.count = 0; + cosmo_stack_unlock(); + cosmo_stack_rehabilitate(stacks); +} + +/** + * Gets maximum number of unused stacks cosmo should cache. + * @see cosmo_stack_setmaxstacks() + */ +int cosmo_stack_getmaxstacks(void) { + return cosmo_stacks_config.maxstacks; +} + +/** + * Sets maximum number of unused stacks cosmo should cache. + * + * This lets you place some limitations on how much stack memory the + * cosmo runtime will cache. This number is a count of stacks rather + * than the number of bytes they contain. Old stacks are freed in a + * least recently used fashion once the cache exceeds this limit. + * + * If this is set to zero, then the cosmo stack allocator enters a + * highly secure hardening mode where cosmo_stack_alloc() zeroes all + * stack memory that's returned. + * + * Setting this to a negative number makes the cache size unlimited. + * + * Please note this limit only applies to stacks that aren't in use. + * + * Your default is three stacks may be cached at any given moment. + * + * If `maxstacks` is less than the current cache size, then surplus + * entries will be evicted and freed before this function returns. + */ +void cosmo_stack_setmaxstacks(int maxstacks) { + cosmo_stack_lock(); + cosmo_stacks_config.maxstacks = maxstacks; + struct Dll *stacks = cosmo_stack_decimate(maxstacks); + cosmo_stack_unlock(); + cosmo_stack_rehabilitate(stacks); +} + +/** + * Allocates stack memory. + * + * This is a caching stack allocator that's used by the POSIX threads + * runtime but you may also find it useful for setcontext() coroutines + * or sigaltstack(). Normally you can get away with using malloc() for + * creating stacks. However some OSes (e.g. OpenBSD) forbid you from + * doing that for anything except sigaltstack(). This API serves to + * abstract all the gory details of gaining authorized memory, and + * additionally implements caching for lightning fast performance. + * + * The stack size must be nonzero. It specifies the minimum amount of + * stack space that will be available for use. The provided value is + * rounded up to the system page size. It may be increased further for + * various reasons. Your stack size parameter will be updated with the + * chosen value upon success. + * + * The guard size specifies the minimum amount of memory that should be + * protected beneath your stack. This helps ensure stack overflows cause + * a segfault rather than corrupting memory silently. This may be set to + * zero in which case no guard pages will be made. This value is rounded + * up to the system page size. The corrected value will be returned upon + * success. Your guard size needs to be small enough to leave room for + * at least one memory page in your stack size i.e. `guardsize + + * pagesize <= stacksize` must be the case. Otherwise this function will + * return an `EINVAL` error. + * + * When you're done using your stack, pass it to cosmo_stack_free() so + * it can be recycled. Stacks are only recycled when the `stacksize` and + * `guardsize` parameters match the constraints described above. Stacks + * that don't end up getting reused will be freed eventually, in a least + * recently used way based upon your cosmo_stack_setmaxstacks() setting. + * + * This function returns 0 on success, or an errno on error. See the + * documentation of mmap() for a list possible errors that may occur. + */ +errno_t cosmo_stack_alloc(size_t *inout_stacksize, // + size_t *inout_guardsize, // + void **out_stackaddr) { + + // validate arguments + size_t stacksize = *inout_stacksize; + size_t guardsize = *inout_guardsize; + stacksize = (stacksize + __pagesize - 1) & -__pagesize; + guardsize = (guardsize + __pagesize - 1) & -__pagesize; + if (!stacksize) + return EINVAL; + + // recycle stack + void *stackaddr = 0; + cosmo_stack_lock(); + for (struct Dll *e = dll_first(cosmo_stacks.stacks); e; + e = dll_next(cosmo_stacks.stacks, e)) { + struct CosmoStack *ts = THREADSTACK_CONTAINER(e); + if (ts->stacksize == stacksize && // + ts->guardsize == guardsize) { + stackaddr = ts->stackaddr; + stacksize = ts->stacksize; + guardsize = ts->guardsize; + dll_remove(&cosmo_stacks.stacks, e); + dll_make_first(&cosmo_stacks.objects, e); + --cosmo_stacks.count; + break; + } + } + cosmo_stack_unlock(); + + // create stack + if (!stackaddr) { + errno_t olde = errno; + if (!IsTiny() && IsLinux() && guardsize && !_weaken(cosmo_dlopen) && + stacksize <= cosmo_stack_maxgrow() && !IsQemuUser()) { + // this special linux-only stack allocator significantly reduces + // the consumption of virtual memory. + if (!(stackaddr = slackmap(stacksize, guardsize))) { + errno_t err = errno; + errno = olde; + return err; + } + } else { + char *map = mmap(0, guardsize + stacksize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (map == MAP_FAILED) { + errno_t err = errno; + errno = olde; + return err; + } + stackaddr = map + guardsize; + if (IsOpenbsd()) + if (!TellOpenbsdThisIsStackMemory(stackaddr, stacksize)) + notpossible; + if (guardsize) { + if (mprotect(map, guardsize, PROT_NONE | PROT_GUARD)) { + errno_t err = errno; + munmap(map, guardsize + stacksize); + errno = olde; + return err; + } + } + } + } + + // return stack + *inout_stacksize = stacksize; + *inout_guardsize = guardsize; + *out_stackaddr = stackaddr; + return 0; +} + +static void cosmo_stack_setup(void) { + atexit(cosmo_stack_clear); +} + +/** + * Frees stack memory. + * + * While not strictly required, it's assumed the three parameters are + * those returned by an earlier call to cosmo_stack_alloc(). If they + * aren't page aligned and rounded, this function will return EINVAL. + * + * This function returns 0 on success, or an errno on error. The `errno` + * variable is never clobbered. You can only dependably count on this to + * return an error on failure when you say `cosmo_stack_setmaxstacks(0)` + */ +errno_t cosmo_stack_free(void *stackaddr, size_t stacksize, size_t guardsize) { + if (!stacksize) + return EINVAL; + if (stacksize & (__pagesize - 1)) + return EINVAL; + if (guardsize & (__pagesize - 1)) + return EINVAL; + if ((uintptr_t)stackaddr & (__pagesize - 1)) + return EINVAL; + cosmo_stack_lock(); + struct Dll *surplus = 0; + if (cosmo_stacks_config.maxstacks) { + cosmo_once(&cosmo_stacks.once, cosmo_stack_setup); + surplus = cosmo_stack_decimate(cosmo_stacks_config.maxstacks - 1); + struct CosmoStack *ts = 0; + if (dll_is_empty(cosmo_stacks.objects)) + cosmo_stack_populate(); + struct Dll *e; + if ((e = dll_first(cosmo_stacks.objects))) { + dll_remove(&cosmo_stacks.objects, e); + ts = THREADSTACK_CONTAINER(e); + } + if (ts) { + ts->stackaddr = stackaddr; + ts->stacksize = stacksize; + ts->guardsize = guardsize; + dll_make_first(&cosmo_stacks.stacks, &ts->elem); + ++cosmo_stacks.count; + stackaddr = 0; + } + } + cosmo_stack_unlock(); + cosmo_stack_rehabilitate(surplus); + errno_t err = 0; + if (stackaddr) + err = cosmo_stack_munmap(stackaddr, stacksize, guardsize); + return err; +} + +relegated bool TellOpenbsdThisIsStackMemory(void *addr, size_t size) { + return __sys_mmap( + addr, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_FIXED | MAP_ANON_OPENBSD | MAP_STACK_OPENBSD, -1, + 0, 0) == addr; +} + +// OpenBSD only permits RSP to occupy memory that's been explicitly +// defined as stack memory, i.e. `lo <= %rsp < hi` must be the case +relegated bool FixupCustomStackOnOpenbsd(pthread_attr_t *attr) { + + // get interval + uintptr_t lo = (uintptr_t)attr->__stackaddr; + uintptr_t hi = lo + attr->__stacksize; + + // squeeze interval + lo = (lo + __pagesize - 1) & -__pagesize; + hi = hi & -__pagesize; + + // tell os it's stack memory + if (!TellOpenbsdThisIsStackMemory((void *)lo, hi - lo)) + return false; + + // update attributes with usable stack address + attr->__stackaddr = (void *)lo; + attr->__stacksize = hi - lo; + return true; +} diff --git a/libc/intrin/stack.h b/libc/intrin/stack.h new file mode 100644 index 000000000..282244547 --- /dev/null +++ b/libc/intrin/stack.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_STACK_H_ +#define COSMOPOLITAN_LIBC_STACK_H_ +#include "libc/thread/thread.h" +COSMOPOLITAN_C_START_ + +void cosmo_stack_lock(void); +void cosmo_stack_unlock(void); +void cosmo_stack_wipe(void); + +bool TellOpenbsdThisIsStackMemory(void *, size_t); +bool FixupCustomStackOnOpenbsd(pthread_attr_t *); + +COSMOPOLITAN_C_END_ +#endif /* COSMOPOLITAN_LIBC_STACK_H_ */ diff --git a/libc/intrin/stdio.c b/libc/intrin/stdio.c new file mode 100644 index 000000000..f487b0867 --- /dev/null +++ b/libc/intrin/stdio.c @@ -0,0 +1,96 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et 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/assert.h" +#include "libc/intrin/atomic.h" +#include "libc/intrin/kprintf.h" +#include "libc/intrin/weaken.h" +#include "libc/mem/mem.h" +#include "libc/stdio/internal.h" +#include "libc/thread/posixthread.internal.h" + +#define STDIO_FILE_USE_AFTER_FREE 1 +#define CORRUPT_STDIO_FILE_OBJECT 1 + +struct Stdio __stdio = { + .lock = PTHREAD_MUTEX_INITIALIZER, +}; + +void __stdio_lock(void) { + _pthread_mutex_lock(&__stdio.lock); +} + +void __stdio_unlock(void) { + _pthread_mutex_unlock(&__stdio.lock); +} + +static int refchk(int refs) { + unassert(refs != STDIO_FILE_USE_AFTER_FREE); + unassert(refs < CORRUPT_STDIO_FILE_OBJECT); + return refs; +} + +void __stdio_ref(FILE *f) { + refchk(atomic_fetch_sub_explicit(&f->refs, 1, memory_order_relaxed)); +} + +static void __stdio_unref_impl(FILE *f, bool should_lock) { + int refs = atomic_load_explicit(&f->refs, memory_order_relaxed); + for (;;) { + refchk(refs); + if (refs) { + if (atomic_compare_exchange_strong_explicit(&f->refs, &refs, refs + 1, + memory_order_acq_rel, + memory_order_relaxed)) + return; + continue; + } + if (should_lock) { + __stdio_lock(); + if ((refs = atomic_load_explicit(&f->refs, memory_order_relaxed))) { + __stdio_unlock(); + continue; + } + } + if (!atomic_compare_exchange_strong_explicit( + &f->refs, &refs, 1, memory_order_acq_rel, memory_order_relaxed)) { + if (should_lock) + __stdio_unlock(); + continue; + } + dll_remove(&__stdio.files, &f->elem); + if (should_lock) + __stdio_unlock(); + break; + } + if (_weaken(free)) { + _weaken(free)(f->getln); + if (f->freebuf) + _weaken(free)(f->buf); + if (f->freethis) + _weaken(free)(f); + } +} + +void __stdio_unref(FILE *f) { + __stdio_unref_impl(f, true); +} + +void __stdio_unref_unlocked(FILE *f) { + __stdio_unref_impl(f, false); +} diff --git a/libc/intrin/strsignal_r.c b/libc/intrin/strsignal_r.c index 325838bf8..24417b36a 100644 --- a/libc/intrin/strsignal_r.c +++ b/libc/intrin/strsignal_r.c @@ -36,12 +36,10 @@ privileged const char *strsignal_r(int sig, char buf[21]) { char *p; const char *s; - if (!sig) { + if (!sig) return "0"; - } - if ((s = GetMagnumStr(kSignalNames, sig))) { + if ((s = GetMagnumStr(kSignalNames, sig))) return s; - } if (SIGRTMIN <= sig && sig <= SIGRTMAX) { sig -= SIGRTMIN; buf[0] = 'S'; diff --git a/libc/calls/swapcontext.S b/libc/intrin/swapcontext.S similarity index 95% rename from libc/calls/swapcontext.S rename to libc/intrin/swapcontext.S index d7b96556f..b40b86777 100644 --- a/libc/calls/swapcontext.S +++ b/libc/intrin/swapcontext.S @@ -31,17 +31,17 @@ // @returnstwice .ftrace1 swapcontext: + beg .ftrace2 -#include "libc/calls/getcontext.inc" +#include "libc/intrin/getcontext.inc" #ifdef __x86_64__ - push %rbp - mov %rsp,%rbp - push %rsi - push %rsi + pro + cpush %rsi + cpush %rsi call __swapcontextsig - pop %rdi - pop %rdi - pop %rbp + cpop %rdi + cpop %rdi + epi test %eax,%eax jnz 1f #elif defined(__aarch64__) @@ -56,4 +56,5 @@ swapcontext: #endif jmp __tailcontext 1: ret + end .endfn swapcontext,globl diff --git a/libc/intrin/sys_gettid.greg.c b/libc/intrin/sys_gettid.greg.c index 408025bc0..fbc4dadd0 100644 --- a/libc/intrin/sys_gettid.greg.c +++ b/libc/intrin/sys_gettid.greg.c @@ -25,7 +25,10 @@ __msabi extern typeof(GetCurrentThreadId) *const __imp_GetCurrentThreadId; -int sys_gettid(void) { +// it's important that this be noinstrument because the child process +// created by fork() needs to update this value quickly, since ftrace +// will deadlock __maps_lock() if the wrong tid is accidentally used. +dontinstrument int sys_gettid(void) { int64_t wut; #ifdef __x86_64__ int tid; diff --git a/libc/intrin/sys_sched_yield.S b/libc/intrin/sys_sched_yield.S index 2bfaa7ccb..f78f48712 100644 --- a/libc/intrin/sys_sched_yield.S +++ b/libc/intrin/sys_sched_yield.S @@ -24,9 +24,9 @@ // // @return 0 on success, or -1 w/ errno sys_sched_yield: + beg #ifdef __x86_64__ - push %rbp - mov %rsp,%rbp + pro xor %eax,%eax mov __hostos(%rip),%dl @@ -84,13 +84,16 @@ sys_sched_yield: // fails a positive or negative errno might get returned. #endif -9: leave +9: epi ret #elif defined(__aarch64__) stp x29,x30,[sp,-32]! mov x29,sp + .cfi_adjust_cfa_offset 32 + .cfi_rel_offset x29,16 + .cfi_rel_offset x30,24 mov x3,0 mov x2,0 add x4,sp,16 @@ -101,10 +104,14 @@ sys_sched_yield: mov x16,#0x5d // select(0,0,0,0,&blah) for xnu svc 0 ldp x29,x30,[sp],32 + .cfi_adjust_cfa_offset -32 + .cfi_restore x30 + .cfi_restore x29 ret #else #error "arch unsupported" #endif + end .endfn sys_sched_yield,globl .previous diff --git a/libc/calls/tailcontext.S b/libc/intrin/tailcontext.S similarity index 99% rename from libc/calls/tailcontext.S rename to libc/intrin/tailcontext.S index 071f98067..8ed1b17c9 100644 --- a/libc/calls/tailcontext.S +++ b/libc/intrin/tailcontext.S @@ -57,8 +57,7 @@ __tailcontext: mov 80(%rax),%rsp push 88(%rax) mov 24(%rax),%rdi - - xor %eax,%eax + mov 64(%rax),%rax ret #elif defined(__aarch64__) diff --git a/libc/intrin/terminatethisprocess.c b/libc/intrin/terminatethisprocess.c index cff6d6e79..2f61cdb27 100644 --- a/libc/intrin/terminatethisprocess.c +++ b/libc/intrin/terminatethisprocess.c @@ -16,17 +16,38 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/atomic.h" +#include "libc/calls/sig.internal.h" +#include "libc/limits.h" +#include "libc/nt/files.h" +#include "libc/nt/memory.h" #include "libc/nt/runtime.h" #include "libc/nt/thunk/msabi.h" +#include "libc/runtime/internal.h" +#ifdef __x86_64__ +__msabi extern typeof(DeleteFile) *const __imp_DeleteFileW; __msabi extern typeof(TerminateProcess) *const __imp_TerminateProcess; +__msabi extern typeof(UnmapViewOfFile) *const __imp_UnmapViewOfFile; /** * Terminates the calling process and all of its threads. */ textwindows dontinstrument void TerminateThisProcess(uint32_t dwWaitStatus) { + + // delete sig file + char16_t path[128]; + atomic_ulong *real; + atomic_ulong fake = 0; + real = __sig.process; + __sig.process = &fake; + __imp_UnmapViewOfFile(real); + __imp_DeleteFileW(__sig_process_path(path, __pid, false)); + // "When a process terminates itself, TerminateProcess stops execution // of the calling thread and does not return." -Quoth MSDN __imp_TerminateProcess(-1, dwWaitStatus); __builtin_unreachable(); } + +#endif /* __x86_64__ */ diff --git a/libc/calls/timespec_add.c b/libc/intrin/timespec_add.c similarity index 100% rename from libc/calls/timespec_add.c rename to libc/intrin/timespec_add.c diff --git a/libc/calls/timespec_cmp.c b/libc/intrin/timespec_cmp.c similarity index 100% rename from libc/calls/timespec_cmp.c rename to libc/intrin/timespec_cmp.c diff --git a/libc/calls/timespec_frommicros.c b/libc/intrin/timespec_frommicros.c similarity index 100% rename from libc/calls/timespec_frommicros.c rename to libc/intrin/timespec_frommicros.c diff --git a/libc/calls/timespec_frommillis.c b/libc/intrin/timespec_frommillis.c similarity index 100% rename from libc/calls/timespec_frommillis.c rename to libc/intrin/timespec_frommillis.c diff --git a/libc/calls/timespec_fromnanos.c b/libc/intrin/timespec_fromnanos.c similarity index 100% rename from libc/calls/timespec_fromnanos.c rename to libc/intrin/timespec_fromnanos.c diff --git a/libc/calls/timespec_sub.c b/libc/intrin/timespec_sub.c similarity index 100% rename from libc/calls/timespec_sub.c rename to libc/intrin/timespec_sub.c diff --git a/libc/calls/timespec_subz.c b/libc/intrin/timespec_subz.c similarity index 100% rename from libc/calls/timespec_subz.c rename to libc/intrin/timespec_subz.c diff --git a/libc/calls/timespec_tomicros.c b/libc/intrin/timespec_tomicros.c similarity index 100% rename from libc/calls/timespec_tomicros.c rename to libc/intrin/timespec_tomicros.c diff --git a/libc/calls/timespec_tomillis.c b/libc/intrin/timespec_tomillis.c similarity index 100% rename from libc/calls/timespec_tomillis.c rename to libc/intrin/timespec_tomillis.c diff --git a/libc/calls/timespec_tonanos.c b/libc/intrin/timespec_tonanos.c similarity index 100% rename from libc/calls/timespec_tonanos.c rename to libc/intrin/timespec_tonanos.c diff --git a/libc/calls/timespec_totimeval.c b/libc/intrin/timespec_totimeval.c similarity index 100% rename from libc/calls/timespec_totimeval.c rename to libc/intrin/timespec_totimeval.c diff --git a/libc/str/timespectowindowstime.c b/libc/intrin/timespectowindowstime.c similarity index 86% rename from libc/str/timespectowindowstime.c rename to libc/intrin/timespectowindowstime.c index af7cb9507..03e8c631c 100644 --- a/libc/str/timespectowindowstime.c +++ b/libc/intrin/timespectowindowstime.c @@ -17,7 +17,14 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/fmt/wintime.internal.h" +#include "libc/limits.h" +#include "libc/stdckdint.h" -int64_t TimeSpecToWindowsTime(struct timespec t) { - return t.tv_nsec / 100 + (t.tv_sec + MODERNITYSECONDS) * HECTONANOSECONDS; +int64_t TimeSpecToWindowsTime(struct timespec time) { + int64_t wt; + if (ckd_add(&wt, time.tv_sec, MODERNITYSECONDS) || + ckd_mul(&wt, wt, HECTONANOSECONDS) || + ckd_add(&wt, wt, time.tv_nsec / 100)) + wt = INT64_MAX; + return wt; } diff --git a/libc/calls/timeval_add.c b/libc/intrin/timeval_add.c similarity index 100% rename from libc/calls/timeval_add.c rename to libc/intrin/timeval_add.c diff --git a/libc/calls/timeval_cmp.c b/libc/intrin/timeval_cmp.c similarity index 100% rename from libc/calls/timeval_cmp.c rename to libc/intrin/timeval_cmp.c diff --git a/libc/calls/timeval_frommicros.c b/libc/intrin/timeval_frommicros.c similarity index 100% rename from libc/calls/timeval_frommicros.c rename to libc/intrin/timeval_frommicros.c diff --git a/libc/calls/timeval_frommillis.c b/libc/intrin/timeval_frommillis.c similarity index 100% rename from libc/calls/timeval_frommillis.c rename to libc/intrin/timeval_frommillis.c diff --git a/libc/calls/timeval_sub.c b/libc/intrin/timeval_sub.c similarity index 100% rename from libc/calls/timeval_sub.c rename to libc/intrin/timeval_sub.c diff --git a/libc/calls/timeval_subz.c b/libc/intrin/timeval_subz.c similarity index 100% rename from libc/calls/timeval_subz.c rename to libc/intrin/timeval_subz.c diff --git a/libc/calls/timeval_tomicros.c b/libc/intrin/timeval_tomicros.c similarity index 100% rename from libc/calls/timeval_tomicros.c rename to libc/intrin/timeval_tomicros.c diff --git a/libc/calls/timeval_tomillis.c b/libc/intrin/timeval_tomillis.c similarity index 100% rename from libc/calls/timeval_tomillis.c rename to libc/intrin/timeval_tomillis.c diff --git a/libc/calls/timeval_toseconds.c b/libc/intrin/timeval_toseconds.c similarity index 100% rename from libc/calls/timeval_toseconds.c rename to libc/intrin/timeval_toseconds.c diff --git a/libc/str/timevaltowindowstime.c b/libc/intrin/timevaltowindowstime.c similarity index 100% rename from libc/str/timevaltowindowstime.c rename to libc/intrin/timevaltowindowstime.c diff --git a/libc/intrin/tls.c b/libc/intrin/tls.c new file mode 100644 index 000000000..3a6d82db2 --- /dev/null +++ b/libc/intrin/tls.c @@ -0,0 +1,54 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 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/thread/tls.h" +#include "libc/dce.h" + +/** + * Returns location of thread information block. + * + * This should be favored over __get_tls() for .privileged code that + * can't be self-modified by __enable_tls(). + */ +privileged optimizespeed struct CosmoTib *__get_tls_privileged(void) { +#if defined(__x86_64__) + char *tib, *lin = (char *)0x30; + if (IsNetbsd() || IsOpenbsd()) { + asm("mov\t%%fs:(%1),%0" : "=a"(tib) : "r"(lin) : "memory"); + } else { + asm("mov\t%%gs:(%1),%0" : "=a"(tib) : "r"(lin) : "memory"); + if (IsWindows()) + tib = *(char **)(tib + 0x1480 + __tls_index * 8); + } + return (struct CosmoTib *)tib; +#elif defined(__aarch64__) + return __get_tls(); +#endif +} + +#if defined(__x86_64__) +privileged optimizespeed struct CosmoTib *__get_tls_win32(void) { + char *tib, *lin = (char *)0x30; + asm("mov\t%%gs:(%1),%0" : "=a"(tib) : "r"(lin) : "memory"); + tib = *(char **)(tib + 0x1480 + __tls_index * 8); + return (struct CosmoTib *)tib; +} +privileged void __set_tls_win32(void *tls) { + asm("mov\t%1,%%gs:%0" : "=m"(*((long *)0x1480 + __tls_index)) : "r"(tls)); +} +#endif diff --git a/libc/intrin/tree.c b/libc/intrin/tree.c index 23e25f7f5..2c3e3fecc 100644 --- a/libc/intrin/tree.c +++ b/libc/intrin/tree.c @@ -54,7 +54,8 @@ struct Tree *tree_prev(struct Tree *node) { return parent; } -static void tree_rotate_left(struct Tree **root, struct Tree *x) { +dontinstrument static void tree_rotate_left(struct Tree **root, + struct Tree *x) { struct Tree *y = x->right; x->right = tree_get_left(y); if (tree_get_left(y)) @@ -71,7 +72,8 @@ static void tree_rotate_left(struct Tree **root, struct Tree *x) { x->parent = y; } -static void tree_rotate_right(struct Tree **root, struct Tree *y) { +dontinstrument static void tree_rotate_right(struct Tree **root, + struct Tree *y) { struct Tree *x = tree_get_left(y); tree_set_left(y, x->right); if (x->right) @@ -88,7 +90,8 @@ static void tree_rotate_right(struct Tree **root, struct Tree *y) { x->right = y; } -static void tree_rebalance_insert(struct Tree **root, struct Tree *node) { +dontinstrument static void tree_rebalance_insert(struct Tree **root, + struct Tree *node) { struct Tree *uncle; tree_set_red(node, 1); while (node != *root && tree_get_red(node->parent)) { @@ -157,8 +160,8 @@ void tree_insert(struct Tree **root, struct Tree *node, tree_cmp_f *cmp) { } } -static void tree_transplant(struct Tree **root, struct Tree *u, - struct Tree *v) { +dontinstrument static void tree_transplant(struct Tree **root, struct Tree *u, + struct Tree *v) { if (!u->parent) { *root = v; } else if (u == tree_get_left(u->parent)) { @@ -170,8 +173,9 @@ static void tree_transplant(struct Tree **root, struct Tree *u, v->parent = u->parent; } -static void tree_rebalance_remove(struct Tree **root, struct Tree *node, - struct Tree *parent) { +dontinstrument static void tree_rebalance_remove(struct Tree **root, + struct Tree *node, + struct Tree *parent) { struct Tree *sibling; while (node != *root && (!node || !tree_get_red(node))) { if (node == tree_get_left(parent)) { diff --git a/libc/calls/ucontext.c b/libc/intrin/ucontext.c similarity index 97% rename from libc/calls/ucontext.c rename to libc/intrin/ucontext.c index d5ba75a94..f7472c127 100644 --- a/libc/calls/ucontext.c +++ b/libc/intrin/ucontext.c @@ -23,7 +23,7 @@ #include "libc/sysv/consts/sig.h" #include "libc/thread/tls.h" -int __tailcontext(const ucontext_t *); +int __tailcontext(const ucontext_t *) wontreturn; /** * Sets machine context. @@ -40,7 +40,7 @@ int setcontext(const ucontext_t *uc) { } else { sys_sigprocmask(SIG_SETMASK, &uc->uc_sigmask, 0); } - return __tailcontext(uc); + __tailcontext(uc); } int __getcontextsig(ucontext_t *uc) { diff --git a/libc/intrin/ulock.c b/libc/intrin/ulock.c index 906f96ecc..f4da16d18 100644 --- a/libc/intrin/ulock.c +++ b/libc/intrin/ulock.c @@ -17,12 +17,12 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/intrin/ulock.h" -#include "libc/assert.h" -#include "libc/calls/calls.h" #include "libc/calls/syscall_support-sysv.internal.h" -#include "libc/dce.h" +#include "libc/errno.h" #include "libc/intrin/describeflags.h" +#include "libc/intrin/kprintf.h" #include "libc/intrin/strace.h" +#include "libc/intrin/ulock.h" // XNU futexes // https://opensource.apple.com/source/xnu/xnu-7195.50.7.100.1/bsd/sys/ulock.h.auto.html @@ -32,6 +32,26 @@ int sys_ulock_wait(uint32_t operation, void *addr, uint64_t value, uint32_t timeout_micros) asm("sys_futex_cp"); // returns number of other waiters, or -1 w/ errno +// +// - EINTR means a signal handler was called. This is how we support +// things like POSIX thread cancelation. +// +// - EFAULT if XNU couldn't read `addr`. This is normally considered a +// programming error, but with ulock it can actually be a transient +// error due to low memory conditions. Apple recommends retrying. +// +// - ENOMEM means XNU wasn't able to allocate memory for kernel internal +// data structures. Apple doesn't provide any advice on what to do. We +// simply turn this into EAGAIN. +// +// - EAGAIN if XNU told us EFAULT but cosmo believes the address exists. +// This value is also used as a substitute for ENOMEM. +// +// - EINVAL could mean operation is invalid, addr is null or misaligned; +// it could also mean another thread calling ulock on this address was +// configured (via operation) in an inconsistent way. +// +// see also os_sync_wait_on_address.h from xcode sdk int ulock_wait(uint32_t operation, void *addr, uint64_t value, uint32_t timeout_micros) { int rc; @@ -39,13 +59,31 @@ int ulock_wait(uint32_t operation, void *addr, uint64_t value, LOCKTRACE("ulock_wait(%#x, %p, %lx, %u) → ...", operation, addr, value, timeout_micros); rc = sys_ulock_wait(operation, addr, value, timeout_micros); + if (rc == -1) { + if (errno == ENOMEM) + errno = EAGAIN; + if (errno == EFAULT) + if (!kisdangerous(addr)) + errno = EAGAIN; + } LOCKTRACE("ulock_wait(%#x, %p, %lx, %u) → %d% m", operation, addr, value, timeout_micros, rc); return rc; } // returns -errno -int ulock_wake(uint32_t operation, void *addr, uint64_t wake_value) { +// +// - ENOENT means there wasn't anyone to wake +// +// - EINVAL could mean operation is invalid, addr is null or misaligned; +// it could also mean another thread calling ulock on this address was +// configured (via operation) in an inconsistent way. +// +// should be dontinstrument because SiliconThreadMain() calls this from +// a stack managed by apple libc. +// +dontinstrument int ulock_wake(uint32_t operation, void *addr, + uint64_t wake_value) { int rc; rc = __syscall3i(operation, (long)addr, wake_value, 0x2000000 | 516); LOCKTRACE("ulock_wake(%#x, %p, %lx) → %s", operation, addr, wake_value, diff --git a/libc/intrin/umask.c b/libc/intrin/umask.c index c3af0d52c..2bec074a8 100644 --- a/libc/intrin/umask.c +++ b/libc/intrin/umask.c @@ -19,4 +19,4 @@ #include "libc/atomic.h" #include "libc/calls/internal.h" -atomic_int __umask; +atomic_int __umask = 0777; diff --git a/libc/calls/vdsofunc.greg.c b/libc/intrin/vdsofunc.c similarity index 100% rename from libc/calls/vdsofunc.greg.c rename to libc/intrin/vdsofunc.c diff --git a/libc/intrin/virtualalloc.c b/libc/intrin/virtualalloc.c new file mode 100644 index 000000000..6993d8154 --- /dev/null +++ b/libc/intrin/virtualalloc.c @@ -0,0 +1,29 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 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/nt/memory.h" +#include "libc/nt/runtime.h" + +/** + * Allocates memory on The New Technology. + */ +textwindows void *VirtualAlloc(void *lpAddress, uint64_t dwSize, + uint32_t flAllocationType, uint32_t flProtect) { + return VirtualAllocEx(GetCurrentProcess(), lpAddress, dwSize, + flAllocationType, flProtect); +} diff --git a/libc/intrin/virtualallocex.c b/libc/intrin/virtualallocex.c new file mode 100644 index 000000000..77e938819 --- /dev/null +++ b/libc/intrin/virtualallocex.c @@ -0,0 +1,41 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 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/syscall_support-nt.internal.h" +#include "libc/intrin/describeflags.h" +#include "libc/intrin/strace.h" +#include "libc/nt/memory.h" +#include "libc/nt/thunk/msabi.h" + +__msabi extern typeof(VirtualAllocEx) *const __imp_VirtualAllocEx; + +/** + * Allocates memory on The New Technology. + */ +textwindows void *VirtualAllocEx(int64_t hProcess, void *lpAddress, + uint64_t dwSize, uint32_t flAllocationType, + uint32_t flProtect) { + void *res = __imp_VirtualAllocEx(hProcess, lpAddress, dwSize, + flAllocationType, flProtect); + if (!res) + __winerr(); + NTTRACE("VirtualAllocEx(%ld, %p, %'lu, %s, %s) → %p% m", hProcess, lpAddress, + dwSize, DescribeNtAllocationType(flAllocationType), + DescribeNtPageFlags(flProtect), res); + return res; +} diff --git a/libc/intrin/virtualmax.c b/libc/intrin/virtualmax.c index 4f24070e2..e6b5b1888 100644 --- a/libc/intrin/virtualmax.c +++ b/libc/intrin/virtualmax.c @@ -19,4 +19,3 @@ #include "libc/runtime/runtime.h" size_t __virtualmax = -1; -size_t __virtualsize = 0; diff --git a/libc/intrin/virtualprotect.c b/libc/intrin/virtualprotect.c index 4b1aaa1a0..5f653afff 100644 --- a/libc/intrin/virtualprotect.c +++ b/libc/intrin/virtualprotect.c @@ -16,13 +16,8 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/syscall_support-nt.internal.h" -#include "libc/intrin/describeflags.h" -#include "libc/intrin/strace.h" -#include "libc/log/libfatal.internal.h" #include "libc/nt/memory.h" - -__msabi extern typeof(VirtualProtect) *const __imp_VirtualProtect; +#include "libc/nt/runtime.h" /** * Protects memory on the New Technology. @@ -31,12 +26,6 @@ __msabi extern typeof(VirtualProtect) *const __imp_VirtualProtect; textwindows bool32 VirtualProtect(void *lpAddress, uint64_t dwSize, uint32_t flNewProtect, uint32_t *lpflOldProtect) { - bool32 bOk; - bOk = __imp_VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect); - if (!bOk) - __winerr(); - NTTRACE("VirtualProtect(%p, %'zu, %s, [%s]) → %hhhd% m", lpAddress, dwSize, - DescribeNtPageFlags(flNewProtect), - DescribeNtPageFlags(*lpflOldProtect), bOk); - return bOk; + return VirtualProtectEx(GetCurrentProcess(), lpAddress, dwSize, flNewProtect, + lpflOldProtect); } diff --git a/libc/stdio/fflushimpl.c b/libc/intrin/virtualprotectex.c similarity index 63% rename from libc/stdio/fflushimpl.c rename to libc/intrin/virtualprotectex.c index 41e047f01..44615c730 100644 --- a/libc/stdio/fflushimpl.c +++ b/libc/intrin/virtualprotectex.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │ vi: set et 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,41 +16,28 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/calls.h" -#include "libc/errno.h" -#include "libc/intrin/weaken.h" -#include "libc/mem/mem.h" -#include "libc/runtime/runtime.h" -#include "libc/stdio/internal.h" -#include "libc/stdio/stdio.h" -#include "libc/sysv/consts/o.h" +#include "libc/calls/syscall_support-nt.internal.h" +#include "libc/intrin/describeflags.h" +#include "libc/intrin/strace.h" +#include "libc/log/libfatal.internal.h" +#include "libc/nt/memory.h" -int __fflush_impl(FILE *f) { - size_t i; - ssize_t rc; - if (f->getln) { - if (_weaken(free)) { - _weaken(free)(f->getln); - } - f->getln = 0; - } - if (f->fd != -1) { - if (f->beg && !f->end && (f->iomode & O_ACCMODE) != O_RDONLY) { - for (i = 0; i < f->beg; i += rc) { - if ((rc = write(f->fd, f->buf + i, f->beg - i)) == -1) { - f->state = errno; - return -1; - } - } - f->beg = 0; - } - if (f->beg < f->end && (f->iomode & O_ACCMODE) != O_WRONLY) { - if (lseek(f->fd, -(int)(f->end - f->beg), SEEK_CUR) == -1) { - f->state = errno; - return -1; - } - f->end = f->beg; - } - } - return 0; +__msabi extern typeof(VirtualProtectEx) *const __imp_VirtualProtectEx; + +/** + * Protects memory on the New Technology. + * @note this wrapper takes care of ABI, STRACE(), and __winerr() + */ +textwindows bool32 VirtualProtectEx(int64_t hProcess, void *lpAddress, + uint64_t dwSize, uint32_t flNewProtect, + uint32_t *lpflOldProtect) { + bool32 bOk; + bOk = __imp_VirtualProtectEx(hProcess, lpAddress, dwSize, flNewProtect, + lpflOldProtect); + if (!bOk) + __winerr(); + NTTRACE("VirtualProtectEx(%ld, %p, %'zu, %s, [%s]) → %hhhd% m", hProcess, + lpAddress, dwSize, DescribeNtPageFlags(flNewProtect), + DescribeNtPageFlags(*lpflOldProtect), bOk); + return bOk; } diff --git a/libc/str/windowsdurationtotimespec.c b/libc/intrin/windowsdurationtotimespec.c similarity index 100% rename from libc/str/windowsdurationtotimespec.c rename to libc/intrin/windowsdurationtotimespec.c diff --git a/libc/str/windowsdurationtotimeval.c b/libc/intrin/windowsdurationtotimeval.c similarity index 100% rename from libc/str/windowsdurationtotimeval.c rename to libc/intrin/windowsdurationtotimeval.c diff --git a/libc/str/windowstimetotimespec.c b/libc/intrin/windowstimetotimespec.c similarity index 100% rename from libc/str/windowstimetotimespec.c rename to libc/intrin/windowstimetotimespec.c diff --git a/libc/str/windowstimetotimeval.c b/libc/intrin/windowstimetotimeval.c similarity index 100% rename from libc/str/windowstimetotimeval.c rename to libc/intrin/windowstimetotimeval.c diff --git a/libc/intrin/winerr.greg.c b/libc/intrin/winerr.greg.c index b960296a1..68abab78e 100644 --- a/libc/intrin/winerr.greg.c +++ b/libc/intrin/winerr.greg.c @@ -24,7 +24,7 @@ #include "libc/nt/runtime.h" #include "libc/sock/internal.h" #include "libc/sysv/errfuns.h" -#include "libc/thread/tls2.internal.h" +#include "libc/thread/tls.h" /** * Return path for failed Win32 API calls. @@ -32,7 +32,7 @@ * @return -1 w/ few exceptions * @note this is a code-size saving device */ -privileged int64_t __winerr(void) { +privileged optimizesize int64_t __winerr(void) { errno_t e; if (IsWindows()) { e = __dos2errno(__imp_GetLastError()); diff --git a/libc/intrin/wintlsinit.c b/libc/intrin/wintlsinit.c index 599bffb13..eb19331ff 100644 --- a/libc/intrin/wintlsinit.c +++ b/libc/intrin/wintlsinit.c @@ -16,12 +16,13 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/atomic.h" #include "libc/log/libfatal.internal.h" #include "libc/nt/thread.h" #include "libc/nt/thunk/msabi.h" #include "libc/runtime/runtime.h" #include "libc/thread/tls.h" -#include "libc/thread/tls2.internal.h" +#ifdef __x86_64__ __msabi extern typeof(GetCurrentThreadId) *const __imp_GetCurrentThreadId; @@ -34,10 +35,14 @@ textwindows dontinstrument void __bootstrap_tls(struct CosmoTib *tib, tib->tib_self = tib; tib->tib_self2 = tib; tib->tib_sigmask = -1; - tib->tib_strace = __strace; - tib->tib_ftrace = __ftrace; + tib->tib_strace = -100; + tib->tib_ftrace = -100; tib->tib_sigstack_size = 57344; tib->tib_sigstack_addr = bp - 57344; - tib->tib_tid = __imp_GetCurrentThreadId(); + int tid = __imp_GetCurrentThreadId(); + atomic_init(&tib->tib_ptid, tid); + atomic_init(&tib->tib_ctid, tid); __set_tls_win32(tib); } + +#endif /* __x86_64__ */ diff --git a/libc/intrin/writeprocessmemory.c b/libc/intrin/writeprocessmemory.c new file mode 100644 index 000000000..ec99b583b --- /dev/null +++ b/libc/intrin/writeprocessmemory.c @@ -0,0 +1,36 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 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/syscall_support-nt.internal.h" +#include "libc/intrin/strace.h" +#include "libc/nt/memory.h" +#include "libc/nt/thunk/msabi.h" + +__msabi extern typeof(WriteProcessMemory) *const __imp_WriteProcessMemory; + +bool32 WriteProcessMemory(int64_t hProcess, void *lpBaseAddress, + const void *lpBuffer, uint64_t nSize, + uint64_t *opt_out_lpNumberOfBytesWritten) { + bool32 ok = __imp_WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, + opt_out_lpNumberOfBytesWritten); + if (!ok) + __winerr(); + NTTRACE("WriteProcessMemory(%ld, %p, %p, %'lu, %p) → %hhhd% m", hProcess, + lpBaseAddress, lpBuffer, nSize, opt_out_lpNumberOfBytesWritten, ok); + return ok; +} diff --git a/libc/irq/acpi-xsdt.c b/libc/irq/acpi-xsdt.c index 44c2a6da5..83b71ffd1 100644 --- a/libc/irq/acpi-xsdt.c +++ b/libc/irq/acpi-xsdt.c @@ -58,9 +58,8 @@ textstartup void *_AcpiOsMapUncachedMemory(uintptr_t phy, size_t n) { } textstartup static void *_AcpiOsAllocatePages(size_t n) { - struct DirectMap dm = sys_mmap_metal(NULL, n, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - void *addr = dm.addr; + void *addr = sys_mmap_metal(NULL, n, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (addr == (void *)-1) addr = NULL; return addr; diff --git a/libc/isystem/complex.h b/libc/isystem/complex.h index 80c28cbf4..417707176 100644 --- a/libc/isystem/complex.h +++ b/libc/isystem/complex.h @@ -1,6 +1,6 @@ #ifndef _COMPLEX_H #define _COMPLEX_H +#include #include "libc/complex.h" #include "libc/imag.h" -#include "libc/math.h" #endif /* _COMPLEX_H */ diff --git a/libc/isystem/float.h b/libc/isystem/float.h index c1effda08..a5cf995a2 100644 --- a/libc/isystem/float.h +++ b/libc/isystem/float.h @@ -1,5 +1,5 @@ #ifndef _FLOAT_H #define _FLOAT_H -#include "libc/math.h" +#include #include "libc/runtime/fenv.h" #endif /* _FLOAT_H */ diff --git a/libc/isystem/windowsesque.h b/libc/isystem/windowsesque.h index f228173de..4b27c516c 100644 --- a/libc/isystem/windowsesque.h +++ b/libc/isystem/windowsesque.h @@ -12,6 +12,7 @@ #include "libc/nt/files.h" #include "libc/nt/ipc.h" #include "libc/nt/memory.h" +#include "libc/nt/nls.h" #include "libc/nt/paint.h" #include "libc/nt/process.h" #include "libc/nt/registry.h" @@ -1420,6 +1421,15 @@ #define HKEY_CURRENT_CONFIG kNtHkeyCurrentConfig #define HKEY_DYN_DATA kNtHkeyDynData #define HKEY_CURRENT_USER_LOCAL_SETTINGS kNtHkeyCurrentUserLocalSettings +#define KEY_QUERY_VALUE kNtKeyQueryValue +#define KEY_SET_VALUE kNtKeySetValue +#define KEY_CREATE_SUB_KEY kNtKeyCreateSubKey +#define KEY_ENUMERATE_SUB_KEYS kNtKeyEnumerateSubKeys +#define KEY_NOTIFY kNtKeyNotify +#define KEY_CREATE_LINK kNtKeyCreateLink +#define KEY_WOW64_32KEY kNtWow6432Key +#define KEY_WOW64_64KEY kNtWow6464Key +#define KEY_WOW64_RES kNtWow64Res #define KEY_READ kNtKeyRead #define KEY_WRITE kNtKeyWrite #define KEY_EXECUTE kNtKeyExecute @@ -4291,6 +4301,13 @@ #define MAKE_HRESULT(sev,fac,code) ((HRESULT) (((unsigned long)(sev)<<31) | ((unsigned long)(fac)<<16) | ((unsigned long)(code))) ) #define MAKE_SCODE(sev,fac,code) ((SCODE) (((unsigned long)(sev)<<31) | ((unsigned long)(fac)<<16) | ((unsigned long)(code))) ) +#define CP_ACP 0 +#define CP_OEMCP 1 +#define CP_MACCP 2 +#define CP_THREAD_ACP 3 +#define CP_SYMBOL 42 + +#define CP_UTF7 65000 #define CP_UTF8 65001 #endif /* COSMOPOLITAN_LIBC_COMPAT_INCLUDE_WINDOWS_H_ */ diff --git a/libc/log/backtrace3.c b/libc/log/backtrace3.c index 967a31a2b..2714e7d33 100644 --- a/libc/log/backtrace3.c +++ b/libc/log/backtrace3.c @@ -25,10 +25,12 @@ #include "libc/intrin/weaken.h" #include "libc/log/backtrace.internal.h" #include "libc/macros.h" +#include "libc/mem/alloca.h" #include "libc/nexgen32e/gc.internal.h" #include "libc/nexgen32e/stackframe.h" #include "libc/runtime/memtrack.internal.h" #include "libc/runtime/runtime.h" +#include "libc/runtime/stack.h" #include "libc/runtime/symbols.internal.h" #include "libc/str/str.h" #include "libc/thread/thread.h" @@ -46,13 +48,13 @@ * @param st is open symbol table for current executable * @return -1 w/ errno if error happened */ -dontinstrument int PrintBacktraceUsingSymbols(int fd, - const struct StackFrame *bp, - struct SymbolTable *st) { +int PrintBacktraceUsingSymbols(int fd, const struct StackFrame *bp, + struct SymbolTable *st) { size_t gi; + char *cxxbuf; intptr_t addr; const char *name; - char cxxbuf[3000]; + int cxxbufsize = 0; int i, symbol, addend; struct Garbages *garbage; const struct StackFrame *frame; @@ -91,14 +93,25 @@ dontinstrument int PrintBacktraceUsingSymbols(int fd, symbol = 0; addend = 0; } - if ((name = __get_symbol_name(st, symbol)) && - (_weaken(__is_mangled) && _weaken(__is_mangled)(name))) { - _weaken(__demangle)(cxxbuf, name, sizeof(cxxbuf)); - kprintf("%012lx %lx %s%+d\n", frame, addr, cxxbuf, addend); - name = cxxbuf; - } else { - kprintf("%012lx %lx %s%+d\n", frame, addr, name, addend); + name = __get_symbol_name(st, symbol); +#pragma GCC push_options +#pragma GCC diagnostic ignored "-Walloca-larger-than=" + // decipher c++ symbols if there's enough stack memory + // stack size requirement assumes max_depth's still 20 + if (_weaken(__demangle) && // + _weaken(__is_mangled) && // + _weaken(__is_mangled)(name)) { + if (!cxxbufsize) + if ((cxxbufsize = __get_safe_size(8192, 8192)) >= 512) { + cxxbuf = alloca(cxxbufsize); + CheckLargeStackAllocation(cxxbuf, sizeof(cxxbufsize)); + } + if (cxxbufsize >= 512) + if (_weaken(__demangle)(cxxbuf, name, cxxbufsize) != -1) + name = cxxbuf; } +#pragma GCC pop_options + kprintf("%012lx %lx %s%+d\n", frame, addr, name, addend); } return 0; } diff --git a/libc/log/gdb.h b/libc/log/gdb.h index 26e252a7c..b7d29fc0c 100644 --- a/libc/log/gdb.h +++ b/libc/log/gdb.h @@ -3,7 +3,7 @@ #include "libc/calls/calls.h" #include "libc/calls/struct/rusage.h" #include "libc/dce.h" -#include "libc/proc/proc.internal.h" +#include "libc/proc/proc.h" #include "libc/sysv/consts/nr.h" #include "libc/sysv/consts/w.h" COSMOPOLITAN_C_START_ diff --git a/libc/log/oncrash_amd64.c b/libc/log/oncrash_amd64.c index 3d174a947..f2726bb19 100644 --- a/libc/log/oncrash_amd64.c +++ b/libc/log/oncrash_amd64.c @@ -20,6 +20,7 @@ #include "libc/atomic.h" #include "libc/calls/blockcancel.internal.h" #include "libc/calls/calls.h" +#include "libc/calls/internal.h" #include "libc/calls/state.internal.h" #include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/siginfo.h" @@ -242,6 +243,10 @@ static relegated void ShowCrashReport(int err, int sig, siginfo_t *si, } kprintf("\n"); __print_maps(15); + if (g_fds.n) + kprintf("\n"); + __printfds(g_fds.p, g_fds.n); + kprintf("\n"); if (__argv) for (i = 0; i < __argc; ++i) kprintf("%s ", __argv[i]); diff --git a/libc/log/oncrash_arm64.c b/libc/log/oncrash_arm64.c index 6658a7c84..c91f39a92 100644 --- a/libc/log/oncrash_arm64.c +++ b/libc/log/oncrash_arm64.c @@ -47,6 +47,7 @@ #include "libc/runtime/runtime.h" #include "libc/runtime/stack.h" #include "libc/runtime/symbols.internal.h" +#include "libc/runtime/syslib.internal.h" #include "libc/stdio/stdio.h" #include "libc/str/str.h" #include "libc/sysv/consts/auxv.h" @@ -396,12 +397,6 @@ relegated void __oncrash(int sig, siginfo_t *si, void *arg) { SpinLock(&lock); __oncrash_impl(sig, si, arg); - // unlike amd64, the instruction pointer on arm64 isn't advanced past - // the debugger breakpoint instruction automatically. we need this so - // execution can resume after __builtin_trap(). - if (arg && sig == SIGTRAP) - ((ucontext_t *)arg)->uc_mcontext.PC += 4; - // ensure execution doesn't resume for anything but SIGTRAP / SIGQUIT if (arg && sig != SIGTRAP && sig != SIGQUIT) { if (!IsXnu()) { diff --git a/libc/log/showcrashreports.c b/libc/log/showcrashreports.c index ff7ea1132..7e3340e64 100644 --- a/libc/log/showcrashreports.c +++ b/libc/log/showcrashreports.c @@ -82,11 +82,7 @@ void ShowCrashReports(void) { ss.ss_sp = crashstack; unassert(!sigaltstack(&ss, 0)); InstallCrashHandler(SIGQUIT, 0); -#ifdef __x86_64__ InstallCrashHandler(SIGTRAP, 0); -#else - InstallCrashHandler(SIGTRAP, 0); -#endif InstallCrashHandler(SIGFPE, 0); InstallCrashHandler(SIGILL, 0); InstallCrashHandler(SIGBUS, 0); diff --git a/libc/log/watch.c b/libc/log/watch.c index 1cc96f767..794242ca2 100644 --- a/libc/log/watch.c +++ b/libc/log/watch.c @@ -33,14 +33,14 @@ static char __watch_last[4096]; void __watch_hook(void); -static dontinstrument inline void Copy(char *p, char *q, size_t n) { +dontinstrument static inline void Copy(char *p, char *q, size_t n) { size_t i; for (i = 0; i < n; ++i) { p[i] = q[i]; } } -static dontinstrument inline int Cmp(char *p, char *q, size_t n) { +dontinstrument static inline int Cmp(char *p, char *q, size_t n) { if (n == 8) return READ64LE(p) != READ64LE(q); if (n == 4) diff --git a/libc/macros.h b/libc/macros.h index cf00d0364..257007a84 100644 --- a/libc/macros.h +++ b/libc/macros.h @@ -158,6 +158,60 @@ .weak \canonical .endm +.macro beg + .cfi_startproc +.endm + +.macro pro +#if defined(__x86_64__) + push %rbp + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rbp,0 + mov %rsp,%rbp + .cfi_def_cfa_register %rbp +#elif defined(__aarch64__) + stp x29,x30,[sp,-16]! + mov x29,sp + .cfi_adjust_cfa_offset 16 + .cfi_rel_offset x29,0 + .cfi_rel_offset x30,8 +#else +#error "unsupported architecture" +#endif +.endm + +.macro epi +#if defined(__x86_64__) + .cfi_def_cfa_register %rsp + leave + .cfi_adjust_cfa_offset -8 + .cfi_restore %rbp +#elif defined(__aarch64__) + ldp x29,x30,[sp],#16 + .cfi_adjust_cfa_offset -16 + .cfi_restore x30 + .cfi_restore x29 +#else +#error "unsupported architecture" +#endif +.endm + +.macro end + .cfi_endproc +.endm + +.macro cpush reg:req + push \reg + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset \reg,0 +.endm + +.macro cpop reg:req + pop \reg + .cfi_adjust_cfa_offset -8 + .cfi_restore \reg +.endm + #ifdef __aarch64__ .macro jmp dest:req b \dest @@ -291,21 +345,6 @@ .balign 4 .endm -// Loads address of errno into %rcx -.macro .errno - call __errno_location -.endm - -// Post-Initialization Read-Only (PIRO) BSS section. -// @param ss is an optional string, for control image locality -.macro .piro ss - .ifnb \ss - .section .piro.sort.bss.\ss,"aw",@nobits - .else - .section .piro.bss,"aw",@nobits - .endif -.endm - // Helpers for Cosmopolitan _init() amalgamation magic. // @param name should be consistent across macros for a module // @see libc/runtime/_init.S diff --git a/libc/mem/calloc.c b/libc/mem/calloc.c index df578353c..d70aefd3e 100644 --- a/libc/mem/calloc.c +++ b/libc/mem/calloc.c @@ -19,6 +19,8 @@ #include "libc/mem/mem.h" #include "third_party/dlmalloc/dlmalloc.h" +__static_yoink("free"); + /** * Allocates n * itemsize bytes, initialized to zero. * @@ -31,4 +33,3 @@ void *calloc(size_t n, size_t itemsize) { return dlcalloc(n, itemsize); } - diff --git a/libc/mem/leaks.c b/libc/mem/leaks.c index 8c7ac7f9d..97febe422 100644 --- a/libc/mem/leaks.c +++ b/libc/mem/leaks.c @@ -16,16 +16,19 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/mem/leaks.h" #include "libc/cxxabi.h" #include "libc/intrin/cxaatexit.h" #include "libc/intrin/dll.h" #include "libc/intrin/kprintf.h" #include "libc/intrin/weaken.h" +#include "libc/macros.h" #include "libc/mem/mem.h" #include "libc/nt/typedef/imagetlscallback.h" #include "libc/runtime/runtime.h" #include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" +#include "libc/thread/tls.h" #define LEAK_CONTAINER(e) DLL_CONTAINER(struct Leak, elem, e) @@ -37,12 +40,12 @@ struct Leak { static int leak_count; static struct Dll *leaks; static struct Dll *freaks; -static pthread_mutex_t lock; +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; void __may_leak(void *alloc) { if (!alloc) return; - pthread_mutex_lock(&lock); + _pthread_mutex_lock(&lock); if (dll_is_empty(freaks)) { int g = __gransize; struct Leak *p = _mapanon(g); @@ -56,7 +59,7 @@ void __may_leak(void *alloc) { LEAK_CONTAINER(e)->alloc = alloc; dll_remove(&freaks, e); dll_make_first(&leaks, e); - pthread_mutex_unlock(&lock); + _pthread_mutex_unlock(&lock); } static void visitor(void *start, void *end, size_t used_bytes, void *arg) { @@ -76,7 +79,7 @@ void CheckForMemoryLeaks(void) { // validate usage of this api if (_weaken(_pthread_decimate)) - _weaken(_pthread_decimate)(false); + _weaken(_pthread_decimate)(kPosixThreadZombie); if (!pthread_orphan_np()) kprintf("warning: called CheckForMemoryLeaks() from non-orphaned thread\n"); @@ -87,8 +90,29 @@ void CheckForMemoryLeaks(void) { // check for leaks malloc_inspect_all(visitor, 0); if (leak_count) { - kprintf("loser: you forgot to call free %'d time%s\n", leak_count, + kprintf(" you forgot to call free %'d time%s\n", leak_count, leak_count == 1 ? "" : "s"); _exit(73); } } + +static bool IsHoldingLocks(struct CosmoTib *tib) { + for (int i = 0; i < ARRAYLEN(tib->tib_locks); ++i) + if (tib->tib_locks[i]) + return true; + return false; +} + +/** + * Aborts if any locks are held by calling thread. + */ +void AssertNoLocksAreHeld(void) { + struct CosmoTib *tib = __get_tls(); + if (IsHoldingLocks(tib)) { + kprintf("error: the following locks are held by this thread:\n"); + for (int i = 0; i < ARRAYLEN(tib->tib_locks); ++i) + if (tib->tib_locks[i]) + kprintf("\t- %t\n", tib->tib_locks[i]); + _Exit(74); + } +} diff --git a/libc/mem/leaks.h b/libc/mem/leaks.h index dcf2ad464..f77c609d2 100644 --- a/libc/mem/leaks.h +++ b/libc/mem/leaks.h @@ -4,6 +4,7 @@ COSMOPOLITAN_C_START_ void CheckForMemoryLeaks(void) libcesque; +void AssertNoLocksAreHeld(void) libcesque; /** * Declares that allocation needn't be freed. diff --git a/libc/mem/malloc.c b/libc/mem/malloc.c index 043a41aac..0d3793cf9 100644 --- a/libc/mem/malloc.c +++ b/libc/mem/malloc.c @@ -19,6 +19,8 @@ #include "libc/mem/mem.h" #include "third_party/dlmalloc/dlmalloc.h" +__static_yoink("free"); + /** * Allocates uninitialized memory. * diff --git a/libc/mem/memalign.c b/libc/mem/memalign.c index bdf8f9ff7..94129aaba 100644 --- a/libc/mem/memalign.c +++ b/libc/mem/memalign.c @@ -19,6 +19,8 @@ #include "libc/mem/mem.h" #include "third_party/dlmalloc/dlmalloc.h" +__static_yoink("free"); + /** * Allocates aligned memory. * @@ -35,4 +37,3 @@ void *memalign(size_t align, size_t bytes) { return dlmemalign(align, bytes); } - diff --git a/libc/mem/realloc.c b/libc/mem/realloc.c index 6d7451a8e..b9a4fe7b4 100644 --- a/libc/mem/realloc.c +++ b/libc/mem/realloc.c @@ -19,6 +19,8 @@ #include "libc/mem/mem.h" #include "third_party/dlmalloc/dlmalloc.h" +__static_yoink("free"); + /** * Allocates / resizes / frees memory, e.g. * @@ -60,4 +62,3 @@ void *realloc(void *p, size_t n) { return dlrealloc(p, n); } - diff --git a/libc/mem/shuffle.internal.h b/libc/mem/shuffle.internal.h deleted file mode 100644 index 2b543a89d..000000000 --- a/libc/mem/shuffle.internal.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_RAND_SHUFFLE_H_ -#define COSMOPOLITAN_LIBC_RAND_SHUFFLE_H_ -#include "libc/intrin/xchg.h" - -/** - * Fisher-Yates shuffle. - * - * @param R is a function like rand() → ≥0 - * @param A is a typed array - * @param n is the number of items in A - * @see ARRAYLEN() - */ -#define shuffle(R, A, n) \ - do { \ - autotype(A) Array = (A); \ - for (size_t i = (n) - 1; i >= 1; --i) { \ - xchg(&Array[i], &Array[R() % (i + 1)]); \ - } \ - } while (0) - -#endif /* COSMOPOLITAN_LIBC_RAND_SHUFFLE_H_ */ diff --git a/libc/nexgen32e/djbsort-avx2.S b/libc/nexgen32e/djbsort-avx2.S index 8f51d678e..70868472d 100644 --- a/libc/nexgen32e/djbsort-avx2.S +++ b/libc/nexgen32e/djbsort-avx2.S @@ -7,8 +7,8 @@ // @note public domain // @see en.wikipedia.org/wiki/Sorting_network djbsort_avx2: - push %rbp - mov %rsp,%rbp + beg + pro push %r15 push %r14 push %r13 @@ -795,11 +795,13 @@ djbsort_avx2: pop %r13 pop %r14 pop %r15 - pop %rbp + epi ret + end .endfn djbsort_avx2,globl,hidden minmax_vector: + beg cmp $7,%rdx jg .L13 .L2: test %rdx,%rdx @@ -838,9 +840,11 @@ minmax_vector: sub $8,%rdx jne .L7 ret + end .endfn minmax_vector int32_twostages_32: + beg sub $-128,%rdi .L17: lea -128(%rdi),%rax test %rsi,%rsi @@ -866,13 +870,14 @@ int32_twostages_32: add $512,%rdi jmp .L17 .L21: ret + end .endfn int32_twostages_32 int32_threestages: - push %rbp + beg + pro imul $-24,%rdx,%r8 lea 0(,%rdx,8),%rax - mov %rsp,%rbp push %r15 push %r14 push %r13 @@ -961,11 +966,13 @@ int32_threestages: pop %r13 pop %r14 pop %r15 - pop %rbp + epi ret + end .endfn int32_threestages merge16_finish: + beg vpminsd %ymm1,%ymm0,%ymm3 vpmaxsd %ymm1,%ymm0,%ymm0 vperm2i128 $32,%ymm0,%ymm3,%ymm2 @@ -994,9 +1001,11 @@ merge16_finish: .L31: vmovdqu %ymm2,(%rdi) vmovdqu %ymm0,32(%rdi) ret + end .endfn merge16_finish int32_sort_2power: + beg push %r13 lea 16(%rsp),%r13 andq $-32,%rsp @@ -2075,6 +2084,7 @@ int32_sort_2power: lea -16(%r13),%rsp pop %r13 ret + end .endfn int32_sort_2power .rodata.cst32 diff --git a/libc/nexgen32e/gc.S b/libc/nexgen32e/gc.S index 4fb1ebff5..1e6f30266 100644 --- a/libc/nexgen32e/gc.S +++ b/libc/nexgen32e/gc.S @@ -32,7 +32,8 @@ // @param rax,rdx,xmm0,xmm1,st0,st1 is return value // @see test/libc/runtime/gc_test.c .ftrace1 -__gc: .ftrace2 +__gc: beg + .ftrace2 #ifdef __x86_64__ @@ -47,8 +48,7 @@ __gc: .ftrace2 mov 8(%r8),%r9 mov 16(%r8),%rdi push 24(%r8) - push %rbp - mov %rsp,%rbp + pro sub $32,%rsp mov %rax,-8(%rbp) mov %rdx,-16(%rbp) @@ -57,7 +57,7 @@ __gc: .ftrace2 movdqa -32(%rbp),%xmm0 mov -16(%rbp),%rdx mov -8(%rbp),%rax - leave + epi ret 9: ud2 nop @@ -66,7 +66,7 @@ __gc: .ftrace2 // if this code fails // check if CosmoTib's size changed - sub x8,x28,#512 // __get_tls() + sub x8,x28,#1024 // __get_tls() ldr x9,[x8,0x18] // tib::garbages ldr x10,[x9] // g->i ldr x8,[x9,8] // g->p @@ -102,4 +102,5 @@ __gc: .ftrace2 #endif /* __x86_64__ */ + end .endfn __gc,globl,hidden diff --git a/libc/nexgen32e/gclongjmp.S b/libc/nexgen32e/gclongjmp.S index 88f534e10..51f93cb15 100644 --- a/libc/nexgen32e/gclongjmp.S +++ b/libc/nexgen32e/gclongjmp.S @@ -31,7 +31,9 @@ // @noreturn .ftrace1 gclongjmp: + beg .ftrace2 + pro #ifdef __x86_64__ push %rbp mov %rsp,%rbp @@ -65,4 +67,5 @@ gclongjmp: #else #error "unsupported architecture" #endif /* __x86_64__ */ + end .endfn gclongjmp,globl diff --git a/libc/nexgen32e/longjmp.S b/libc/nexgen32e/longjmp.S index aa4e0cfc7..5aefd029f 100644 --- a/libc/nexgen32e/longjmp.S +++ b/libc/nexgen32e/longjmp.S @@ -26,7 +26,7 @@ // @see gclongjmp() // @see siglongjmp() .ftrace1 -longjmp: +longjmp:beg .ftrace2 _longjmp: #ifdef __x86_64__ @@ -61,6 +61,7 @@ _longjmp: #else #error "unsupported architecture" #endif + end .endfn longjmp,globl .endfn _longjmp,globl .alias longjmp,siglongjmp diff --git a/libc/nexgen32e/nt2sysv.S b/libc/nexgen32e/nt2sysv.S index e4461d1bb..185687de6 100644 --- a/libc/nexgen32e/nt2sysv.S +++ b/libc/nexgen32e/nt2sysv.S @@ -30,8 +30,8 @@ // @note slower than __sysv2nt // @see NT2SYSV() macro __nt2sysv: - push %rbp - mov %rsp,%rbp + beg + pro sub $256,%rsp push %rbx push %rdi @@ -48,6 +48,7 @@ __nt2sysv: pop %rsi pop %rdi pop %rbx - leave + epi ret + end .endfn __nt2sysv,globl,hidden diff --git a/libc/nexgen32e/pid.c b/libc/nexgen32e/pid.c index 1abfb511e..235e575f1 100644 --- a/libc/nexgen32e/pid.c +++ b/libc/nexgen32e/pid.c @@ -17,5 +17,8 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/runtime/internal.h" +#ifndef __x86_64__ int __pid; + +#endif diff --git a/libc/nexgen32e/threaded.c b/libc/nexgen32e/threaded.c index 1fad2aa80..b2c53384b 100644 --- a/libc/nexgen32e/threaded.c +++ b/libc/nexgen32e/threaded.c @@ -18,8 +18,6 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/thread/tls.h" -#ifdef __x86_64__ -char __tls_enabled; -#endif - +#ifndef __x86_64__ unsigned __tls_index; +#endif diff --git a/libc/nt/BUILD.mk b/libc/nt/BUILD.mk index 7e96e9467..b5660d1be 100644 --- a/libc/nt/BUILD.mk +++ b/libc/nt/BUILD.mk @@ -91,6 +91,27 @@ $(LIBC_NT_COMDLG32_A).pkg: \ #─────────────────────────────────────────────────────────────────────────────── +LIBC_NT_ARTIFACTS += LIBC_NT_SHELL32_A +LIBC_NT_SHELL32 = $(LIBC_NT_SHELL32_A_DEPS) $(LIBC_NT_SHELL32_A) +LIBC_NT_SHELL32_A = o/$(MODE)/libc/nt/shell32.a +LIBC_NT_SHELL32_A_SRCS := $(wildcard libc/nt/shell32/*.S) +LIBC_NT_SHELL32_A_OBJS = $(LIBC_NT_SHELL32_A_SRCS:%.S=o/$(MODE)/%.o) +LIBC_NT_SHELL32_A_CHECKS = $(LIBC_NT_SHELL32_A).pkg +LIBC_NT_SHELL32_A_DIRECTDEPS = LIBC_NT_KERNEL32 +LIBC_NT_SHELL32_A_DEPS := \ + $(call uniq,$(foreach x,$(LIBC_NT_SHELL32_A_DIRECTDEPS),$($(x)))) + +$(LIBC_NT_SHELL32_A): \ + libc/nt/shell32/ \ + $(LIBC_NT_SHELL32_A).pkg \ + $(LIBC_NT_SHELL32_A_OBJS) + +$(LIBC_NT_SHELL32_A).pkg: \ + $(LIBC_NT_SHELL32_A_OBJS) \ + $(foreach x,$(LIBC_NT_SHELL32_A_DIRECTDEPS),$($(x)_A).pkg) + +#─────────────────────────────────────────────────────────────────────────────── + LIBC_NT_ARTIFACTS += LIBC_NT_GDI32_A LIBC_NT_GDI32 = $(LIBC_NT_GDI32_A_DEPS) $(LIBC_NT_GDI32_A) LIBC_NT_GDI32_A = o/$(MODE)/libc/nt/gdi32.a diff --git a/libc/nt/advapi32/RegOpenKeyExA.S b/libc/nt/advapi32/RegOpenKeyExA.S new file mode 100644 index 000000000..31ee26848 --- /dev/null +++ b/libc/nt/advapi32/RegOpenKeyExA.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp advapi32,__imp_RegOpenKeyExA,RegOpenKeyExA + + .text.windows + .ftrace1 +RegOpenKeyExA: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_RegOpenKeyExA(%rip),%rax + jmp __sysv2nt6 +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn RegOpenKeyExA,globl + .previous diff --git a/libc/nt/enum/keyaccess.h b/libc/nt/enum/keyaccess.h index 06709ad42..1abb200a4 100644 --- a/libc/nt/enum/keyaccess.h +++ b/libc/nt/enum/keyaccess.h @@ -1,6 +1,16 @@ #ifndef COSMOPOLITAN_LIBC_NT_ENUM_KEYACCESS_H_ #define COSMOPOLITAN_LIBC_NT_ENUM_KEYACCESS_H_ +#define kNtKeyQueryValue 0x00000001 +#define kNtKeySetValue 0x00000002 +#define kNtKeyCreateSubKey 0x00000004 +#define kNtKeyEnumerateSubKeys 0x00000008 +#define kNtKeyNotify 0x00000010 +#define kNtKeyCreateLink 0x00000020 +#define kNtWow6432Key 0x00000200 +#define kNtWow6464Key 0x00000100 +#define kNtWow64Res 0x00000300 + #define kNtKeyRead 0x00020019 #define kNtKeyWrite 0x00020006 #define kNtKeyExecute 0x00020019 diff --git a/libc/nt/enum/status.h b/libc/nt/enum/status.h index ed3dc8ff3..cc11bc96b 100644 --- a/libc/nt/enum/status.h +++ b/libc/nt/enum/status.h @@ -2,68 +2,68 @@ #define COSMOPOLITAN_LIBC_NT_STATUS_H_ /* high two bits = {success,informational,warning,error} */ -#define kNtStatusSuccess 0x00000000 /* success statuses */ -#define kNtStatusWait0 0x00000000 -#define kNtStatusAbandonedWait0 0x00000080 -#define kNtStatusUserApc 0x000000C0 -#define kNtStatusTimeout 0x00000102 -#define kNtStatusPending 0x00000103 -#define kNtStatusGuardPageViolation 0x80000001 /* warning statuses */ -#define kNtStatusDatatypeMisalignment 0x80000002 -#define kNtStatusBreakpoint 0x80000003 -#define kNtStatusSingleStep 0x80000004 -#define kNtStatusLongjump 0x80000026 -#define kNtStatusUnwindConsolidate 0x80000029 -#define kNtStatusAccessViolation 0xC0000005 /* error statuses */ -#define kNtStatusInPageError 0xC0000006 -#define kNtStatusInvalidHandle 0xC0000008 -#define kNtStatusInvalidParameter 0xC000000D -#define kNtStatusNoMemory 0xC0000017 -#define kNtStatusIllegalInstruction 0xC000001D -#define kNtStatusNoncontinuableException 0xC0000025 -#define kNtStatusInvalidDisposition 0xC0000026 -#define kNtStatusArrayBoundsExceeded 0xC000008C -#define kNtStatusFloatDenormalOperand 0xC000008D -#define kNtStatusFloatDivideByZero 0xC000008E -#define kNtStatusFloatInexactResult 0xC000008F -#define kNtStatusFloatInvalidOperation 0xC0000090 -#define kNtStatusFloatOverflow 0xC0000091 -#define kNtStatusFloatStackCheck 0xC0000092 -#define kNtStatusFloatUnderflow 0xC0000093 -#define kNtStatusIntegerDivideBYZero 0xC0000094 -#define kNtStatusIntegerOverflow 0xC0000095 -#define kNtStatusPrivilegedInstruction 0xC0000096 -#define kNtStatusStackOverflow 0xC00000FD -#define kNtStatusDllNotFound 0xC0000135 -#define kNtStatusOrdinalNotFound 0xC0000138 -#define kNtStatusEntrypointNotFound 0xC0000139 -#define kNtStatusControlCExit 0xC000013A -#define kNtStatusDllInitFailed 0xC0000142 -#define kNtStatusFloatMultipleFaults 0xC00002B4 -#define kNtStatusFloatMultipleTraps 0xC00002B5 -#define kNtStatusRegNatConsumption 0xC00002C9 -#define kNtStatusHeapCorruption 0xC0000374 -#define kNtStatusStackBufferOverrun 0xC0000409 -#define kNtStatusInvalidCruntimeParameter 0xC0000417 -#define kNtStatusAssertionFailure 0xC0000420 -#define kNtStatusEnclaveViolation 0xC00004A2 -#define kNtStatusSegmentNotification 0x40000005 -#define kNtStatusFatalAppExit 0x40000015 -#define kNtStatusNotFound 0xC0000225 -#define kNtStatusCancelled 0xC0000120 +#define kNtStatusSuccess 0x00000000u /* success statuses */ +#define kNtStatusWait0 0x00000000u +#define kNtStatusAbandonedWait0 0x00000080u +#define kNtStatusUserApc 0x000000C0u +#define kNtStatusTimeout 0x00000102u +#define kNtStatusPending 0x00000103u +#define kNtStatusGuardPageViolation 0x80000001u /* warning statuses */ +#define kNtStatusDatatypeMisalignment 0x80000002u +#define kNtStatusBreakpoint 0x80000003u +#define kNtStatusSingleStep 0x80000004u +#define kNtStatusLongjump 0x80000026u +#define kNtStatusUnwindConsolidate 0x80000029u +#define kNtStatusAccessViolation 0xC0000005u /* error statuses */ +#define kNtStatusInPageError 0xC0000006u +#define kNtStatusInvalidHandle 0xC0000008u +#define kNtStatusInvalidParameter 0xC000000Du +#define kNtStatusNoMemory 0xC0000017u +#define kNtStatusIllegalInstruction 0xC000001Du +#define kNtStatusNoncontinuableException 0xC0000025u +#define kNtStatusInvalidDisposition 0xC0000026u +#define kNtStatusArrayBoundsExceeded 0xC000008Cu +#define kNtStatusFloatDenormalOperand 0xC000008Du +#define kNtStatusFloatDivideByZero 0xC000008Eu +#define kNtStatusFloatInexactResult 0xC000008Fu +#define kNtStatusFloatInvalidOperation 0xC0000090u +#define kNtStatusFloatOverflow 0xC0000091u +#define kNtStatusFloatStackCheck 0xC0000092u +#define kNtStatusFloatUnderflow 0xC0000093u +#define kNtStatusIntegerDivideBYZero 0xC0000094u +#define kNtStatusIntegerOverflow 0xC0000095u +#define kNtStatusPrivilegedInstruction 0xC0000096u +#define kNtStatusStackOverflow 0xC00000FDu +#define kNtStatusDllNotFound 0xC0000135u +#define kNtStatusOrdinalNotFound 0xC0000138u +#define kNtStatusEntrypointNotFound 0xC0000139u +#define kNtStatusControlCExit 0xC000013Au +#define kNtStatusDllInitFailed 0xC0000142u +#define kNtStatusFloatMultipleFaults 0xC00002B4u +#define kNtStatusFloatMultipleTraps 0xC00002B5u +#define kNtStatusRegNatConsumption 0xC00002C9u +#define kNtStatusHeapCorruption 0xC0000374u +#define kNtStatusStackBufferOverrun 0xC0000409u +#define kNtStatusInvalidCruntimeParameter 0xC0000417u +#define kNtStatusAssertionFailure 0xC0000420u +#define kNtStatusEnclaveViolation 0xC00004A2u +#define kNtStatusSegmentNotification 0x40000005u +#define kNtStatusFatalAppExit 0x40000015u +#define kNtStatusNotFound 0xC0000225u +#define kNtStatusCancelled 0xC0000120u -#define kNtDbgExceptionHandled 0x00010001 -#define kNtDbgContinue 0x00010002 -#define kNtDbgReplyLater 0x40010001 -#define kNtDbgTerminateThread 0x40010003 -#define kNtDbgTerminateProcess 0x40010004 -#define kNtDbgControlC 0x40010005 -#define kNtDbgPrintexceptionC 0x40010006 -#define kNtDbgRipexception 0x40010007 -#define kNtDbgControlBreak 0x40010008 -#define kNtDbgCommandException 0x40010009 -#define kNtDbgPrintexceptionWideC 0x4001000A -#define kNtDbgExceptionNotHandled 0x80010001 +#define kNtDbgExceptionHandled 0x00010001u +#define kNtDbgContinue 0x00010002u +#define kNtDbgReplyLater 0x40010001u +#define kNtDbgTerminateThread 0x40010003u +#define kNtDbgTerminateProcess 0x40010004u +#define kNtDbgControlC 0x40010005u +#define kNtDbgPrintexceptionC 0x40010006u +#define kNtDbgRipexception 0x40010007u +#define kNtDbgControlBreak 0x40010008u +#define kNtDbgCommandException 0x40010009u +#define kNtDbgPrintexceptionWideC 0x4001000Au +#define kNtDbgExceptionNotHandled 0x80010001u #define kNtStillActive kNtStatusPending #if !(__ASSEMBLER__ + __LINKER__ + 0) diff --git a/libc/nt/files.h b/libc/nt/files.h index fcbc294cb..2b844c32f 100644 --- a/libc/nt/files.h +++ b/libc/nt/files.h @@ -49,6 +49,7 @@ COSMOPOLITAN_C_START_ intptr_t LoadResource(int64_t hModule, int64_t hResInfo); uint32_t SetHandleCount(uint32_t uNumber); uint32_t GetLogicalDrives(void); +uint32_t GetLogicalDriveStringsA(uint32_t nBufferLength, char *lpBuffer); bool32 FlushFileBuffers(int64_t hFile); int64_t ReOpenFile(int64_t hOriginalFile, uint32_t dwDesiredAccess, @@ -205,6 +206,7 @@ uint32_t GetFinalPathNameByHandle(int64_t hFile, char16_t *out_path, uint32_t GetFullPathName(const char16_t *lpFileName, uint32_t nBufferLength, char16_t *lpBuffer, char16_t **lpFilePart); +uint32_t GetShortPathName(const char16_t *lpszLongPath, char16_t *out_lpszShortPath, uint32_t cchBuffer); bool32 GetOverlappedResult(int64_t hFile, struct NtOverlapped *lpOverlapped, uint32_t *lpNumberOfBytesTransferred, bool32 bWait); @@ -225,6 +227,10 @@ bool32 GetVolumeInformationByHandle(int64_t hFile, char16_t *opt_out_lpFileSystemNameBuffer, uint32_t nFileSystemNameSize); +uint32_t SetFilePointer(intptr_t hFile, int32_t lDistanceToMove, + long *opt_inout_lpDistanceToMoveHigh, + uint32_t dwMoveMethod); + #if ShouldUseMsabiAttribute() #include "libc/nt/thunk/files.inc" #endif /* ShouldUseMsabiAttribute() */ diff --git a/libc/nt/gdi32/DescribePixelFormat.S b/libc/nt/gdi32/DescribePixelFormat.S new file mode 100644 index 000000000..44b3dc746 --- /dev/null +++ b/libc/nt/gdi32/DescribePixelFormat.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp gdi32,__imp_DescribePixelFormat,DescribePixelFormat + + .text.windows + .ftrace1 +DescribePixelFormat: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_DescribePixelFormat(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn DescribePixelFormat,globl + .previous diff --git a/libc/nt/kernel32/GetACP.S b/libc/nt/kernel32/GetACP.S new file mode 100644 index 000000000..f0121f7e0 --- /dev/null +++ b/libc/nt/kernel32/GetACP.S @@ -0,0 +1,19 @@ +#include "libc/nt/codegen.h" +.imp kernel32,__imp_GetACP,GetACP + + .text.windows + .ftrace1 +GetACP: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + sub $32,%rsp + call *__imp_GetACP(%rip) + leave +#elif defined(__aarch64__) + mov x0,#0 +#endif + ret + .endfn GetACP,globl + .previous diff --git a/libc/nt/kernel32/VirtualAlloc.S b/libc/nt/kernel32/GetCPInfoExW.S similarity index 60% rename from libc/nt/kernel32/VirtualAlloc.S rename to libc/nt/kernel32/GetCPInfoExW.S index f8e5f815a..a58310911 100644 --- a/libc/nt/kernel32/VirtualAlloc.S +++ b/libc/nt/kernel32/GetCPInfoExW.S @@ -1,18 +1,18 @@ #include "libc/nt/codegen.h" -.imp kernel32,__imp_VirtualAlloc,VirtualAlloc +.imp kernel32,__imp_GetCPInfoExW,GetCPInfoExW .text.windows .ftrace1 -VirtualAlloc: +GetCPInfoEx: .ftrace2 #ifdef __x86_64__ push %rbp mov %rsp,%rbp - mov __imp_VirtualAlloc(%rip),%rax + mov __imp_GetCPInfoExW(%rip),%rax jmp __sysv2nt #elif defined(__aarch64__) mov x0,#0 ret #endif - .endfn VirtualAlloc,globl + .endfn GetCPInfoEx,globl .previous diff --git a/libc/nt/kernel32/GetLogicalDriveStringsA.S b/libc/nt/kernel32/GetLogicalDriveStringsA.S new file mode 100644 index 000000000..de327c7fc --- /dev/null +++ b/libc/nt/kernel32/GetLogicalDriveStringsA.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp kernel32,__imp_GetLogicalDriveStringsA,GetLogicalDriveStringsA + + .text.windows + .ftrace1 +GetLogicalDriveStringsA: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_GetLogicalDriveStringsA(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn GetLogicalDriveStringsA,globl + .previous diff --git a/libc/nt/kernel32/GetOEMCP.S b/libc/nt/kernel32/GetOEMCP.S new file mode 100644 index 000000000..18227546f --- /dev/null +++ b/libc/nt/kernel32/GetOEMCP.S @@ -0,0 +1,19 @@ +#include "libc/nt/codegen.h" +.imp kernel32,__imp_GetOEMCP,GetOEMCP + + .text.windows + .ftrace1 +GetOEMCP: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + sub $32,%rsp + call *__imp_GetOEMCP(%rip) + leave +#elif defined(__aarch64__) + mov x0,#0 +#endif + ret + .endfn GetOEMCP,globl + .previous diff --git a/libc/nt/kernel32/GetShortPathNameW.S b/libc/nt/kernel32/GetShortPathNameW.S new file mode 100644 index 000000000..d0c28f2f6 --- /dev/null +++ b/libc/nt/kernel32/GetShortPathNameW.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp kernel32,__imp_GetShortPathNameW,GetShortPathNameW + + .text.windows + .ftrace1 +GetShortPathName: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_GetShortPathNameW(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn GetShortPathName,globl + .previous diff --git a/libc/nt/kernel32/GlobalLock.S b/libc/nt/kernel32/GlobalLock.S new file mode 100644 index 000000000..1407a4427 --- /dev/null +++ b/libc/nt/kernel32/GlobalLock.S @@ -0,0 +1,20 @@ +#include "libc/nt/codegen.h" +.imp kernel32,__imp_GlobalLock,GlobalLock + + .text.windows + .ftrace1 +GlobalLock: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov %rdi,%rcx + sub $32,%rsp + call *__imp_GlobalLock(%rip) + leave +#elif defined(__aarch64__) + mov x0,#0 +#endif + ret + .endfn GlobalLock,globl + .previous diff --git a/libc/nt/kernel32/GlobalUnlock.S b/libc/nt/kernel32/GlobalUnlock.S new file mode 100644 index 000000000..b9ba550f8 --- /dev/null +++ b/libc/nt/kernel32/GlobalUnlock.S @@ -0,0 +1,20 @@ +#include "libc/nt/codegen.h" +.imp kernel32,__imp_GlobalUnlock,GlobalUnlock + + .text.windows + .ftrace1 +GlobalUnlock: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov %rdi,%rcx + sub $32,%rsp + call *__imp_GlobalUnlock(%rip) + leave +#elif defined(__aarch64__) + mov x0,#0 +#endif + ret + .endfn GlobalUnlock,globl + .previous diff --git a/libc/nt/kernel32/SetFilePointer.S b/libc/nt/kernel32/SetFilePointer.S new file mode 100644 index 000000000..2e14b9e3e --- /dev/null +++ b/libc/nt/kernel32/SetFilePointer.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp kernel32,__imp_SetFilePointer,SetFilePointer + + .text.windows + .ftrace1 +SetFilePointer: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_SetFilePointer(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn SetFilePointer,globl + .previous diff --git a/libc/nt/kernel32/VirtualAllocEx.S b/libc/nt/kernel32/VirtualAllocEx.S index bdf00950b..239913a84 100644 --- a/libc/nt/kernel32/VirtualAllocEx.S +++ b/libc/nt/kernel32/VirtualAllocEx.S @@ -1,18 +1,2 @@ #include "libc/nt/codegen.h" .imp kernel32,__imp_VirtualAllocEx,VirtualAllocEx - - .text.windows - .ftrace1 -VirtualAllocEx: - .ftrace2 -#ifdef __x86_64__ - push %rbp - mov %rsp,%rbp - mov __imp_VirtualAllocEx(%rip),%rax - jmp __sysv2nt6 -#elif defined(__aarch64__) - mov x0,#0 - ret -#endif - .endfn VirtualAllocEx,globl - .previous diff --git a/libc/nt/kernel32/VirtualProtectEx.S b/libc/nt/kernel32/VirtualProtectEx.S new file mode 100644 index 000000000..8d22b1789 --- /dev/null +++ b/libc/nt/kernel32/VirtualProtectEx.S @@ -0,0 +1,2 @@ +#include "libc/nt/codegen.h" +.imp kernel32,__imp_VirtualProtectEx,VirtualProtectEx diff --git a/libc/nt/kernel32/VirtualQueryEx.S b/libc/nt/kernel32/VirtualQueryEx.S new file mode 100644 index 000000000..d810cf97a --- /dev/null +++ b/libc/nt/kernel32/VirtualQueryEx.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp kernel32,__imp_VirtualQueryEx,VirtualQueryEx + + .text.windows + .ftrace1 +VirtualQueryEx: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_VirtualQueryEx(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn VirtualQueryEx,globl + .previous diff --git a/libc/nt/kernel32/WriteProcessMemory.S b/libc/nt/kernel32/WriteProcessMemory.S new file mode 100644 index 000000000..222dd5e72 --- /dev/null +++ b/libc/nt/kernel32/WriteProcessMemory.S @@ -0,0 +1,2 @@ +#include "libc/nt/codegen.h" +.imp kernel32,__imp_WriteProcessMemory,WriteProcessMemory diff --git a/libc/nt/master.sh b/libc/nt/master.sh index c79782d21..570a77e72 100755 --- a/libc/nt/master.sh +++ b/libc/nt/master.sh @@ -40,9 +40,12 @@ imp '' SetCurrentDirectoryW kernel32 1 imp '' TerminateProcess kernel32 2 imp '' UnlockFileEx kernel32 5 imp '' UnmapViewOfFile kernel32 1 +imp '' VirtualAllocEx kernel32 5 imp '' VirtualProtect kernel32 4 +imp '' VirtualProtectEx kernel32 5 imp '' WaitForMultipleObjects kernel32 4 imp '' WaitForSingleObject kernel32 2 +imp '' WriteProcessMemory kernel32 5 imp 'AcquireSRWLockExclusive' AcquireSRWLockExclusive kernel32 1 imp 'AcquireSRWLockShared' AcquireSRWLockShared kernel32 1 imp 'AddDllDirectory' AddDllDirectory kernel32 1 @@ -126,10 +129,12 @@ imp 'GetFileTime' GetFileTime kernel32 4 imp 'GetFileType' GetFileType kernel32 1 imp 'GetFinalPathNameByHandle' GetFinalPathNameByHandleW kernel32 4 imp 'GetFullPathName' GetFullPathNameW kernel32 4 +imp 'GetShortPathName' GetShortPathNameW kernel32 3 imp 'GetHandleInformation' GetHandleInformation kernel32 2 imp 'GetLargestConsoleWindowSize' GetLargestConsoleWindowSize kernel32 1 imp 'GetLastError' GetLastError kernel32 0 imp 'GetLogicalDrives' GetLogicalDrives kernel32 0 +imp 'GetLogicalDriveStringsA' GetLogicalDriveStringsA kernel32 2 imp 'GetMaximumProcessorCount' GetMaximumProcessorCount kernel32 1 # Windows 7+ imp 'GetModuleFileName' GetModuleFileNameW kernel32 3 imp 'GetModuleHandle' GetModuleHandleA kernel32 1 @@ -183,9 +188,14 @@ imp 'GetVolumeInformationByHandle' GetVolumeInformationByHandleW kernel32 imp 'GetVolumePathName' GetVolumePathNameW kernel32 3 imp 'GetWindowsDirectory' GetWindowsDirectoryW kernel32 2 imp 'GetWindowsDirectoryA' GetWindowsDirectoryA kernel32 2 +imp 'GetOEMCP' GetOEMCP kernel32 0 +imp 'GetACP' GetACP kernel32 0 +imp 'GetCPInfoEx' GetCPInfoExW kernel32 3 imp 'GlobalAlloc' GlobalAlloc kernel32 2 imp 'GlobalFree' GlobalFree kernel32 1 +imp 'GlobalLock' GlobalLock kernel32 1 imp 'GlobalMemoryStatusEx' GlobalMemoryStatusEx kernel32 1 +imp 'GlobalUnlock' GlobalUnlock kernel32 1 imp 'HeapAlloc' HeapAlloc kernel32 3 imp 'HeapCompact' HeapCompact kernel32 2 imp 'HeapCreate' HeapCreate kernel32 3 @@ -259,6 +269,7 @@ imp 'SetEvent' SetEvent kernel32 1 imp 'SetFileAttributes' SetFileAttributesW kernel32 2 imp 'SetFileCompletionNotificationModes' SetFileCompletionNotificationModes kernel32 2 imp 'SetFileInformationByHandle' SetFileInformationByHandle kernel32 4 +imp 'SetFilePointer' SetFilePointer kernel32 4 imp 'SetFileTime' SetFileTime kernel32 4 imp 'SetFileValidData' SetFileValidData kernel32 2 imp 'SetHandleCount' SetHandleCount kernel32 1 @@ -296,11 +307,10 @@ imp 'UnlockFile' UnlockFile kernel32 5 imp 'UnmapViewOfFile2' UnmapViewOfFile2 kernel32 2 imp 'UnmapViewOfFileEx' UnmapViewOfFileEx kernel32 3 imp 'UpdateProcThreadAttribute' UpdateProcThreadAttribute kernel32 7 -imp 'VirtualAlloc' VirtualAlloc kernel32 4 -imp 'VirtualAllocEx' VirtualAllocEx kernel32 5 imp 'VirtualFree' VirtualFree kernel32 3 imp 'VirtualLock' VirtualLock kernel32 2 imp 'VirtualQuery' VirtualQuery kernel32 3 +imp 'VirtualQueryEx' VirtualQueryEx kernel32 4 imp 'VirtualUnlock' VirtualUnlock kernel32 2 imp 'WaitForMultipleObjectsEx' WaitForMultipleObjectsEx kernel32 5 imp 'WaitForSingleObjectEx' WaitForSingleObjectEx kernel32 3 @@ -351,6 +361,7 @@ imp 'RegLoadKey' RegLoadKeyW advapi32 3 imp 'RegNotifyChangeKeyValue' RegNotifyChangeKeyValue advapi32 5 imp 'RegOpenCurrentUser' RegOpenCurrentUser advapi32 2 imp 'RegOpenKeyEx' RegOpenKeyExW advapi32 5 +imp 'RegOpenKeyExA' RegOpenKeyExA advapi32 5 imp 'RegOpenUserClassesRoot' RegOpenUserClassesRoot advapi32 4 imp 'RegOverridePredefKey' RegOverridePredefKey advapi32 2 imp 'RegQueryInfoKey' RegQueryInfoKeyW advapi32 12 @@ -375,6 +386,7 @@ imp 'TraceSetInformation' TraceSetInformation advapi32 # Windows 7+ # # Name Actual DLL Arity imp 'AdjustWindowRect' AdjustWindowRect user32 3 +imp 'AdjustWindowRectEx' AdjustWindowRectEx user32 4 imp 'AnimateWindow' AnimateWindow user32 3 imp 'AppendMenuA' AppendMenuA user32 4 imp 'AppendMenu' AppendMenuW user32 4 @@ -382,6 +394,9 @@ imp 'BeginPaint' BeginPaint user32 2 imp 'BringWindowToTop' BringWindowToTop user32 1 imp 'CallNextHookEx' CallNextHookEx user32 4 imp 'CloseWindow' CloseWindow user32 1 +imp 'ClientToScreen' ClientToScreen user32 2 +imp 'ClipCursor' ClipCursor user32 1 +imp 'CloseClipboard' CloseClipboard user32 0 imp 'CreateIconIndirect' CreateIconIndirect user32 1 imp 'CreateMenu' CreateMenu user32 0 imp 'CreatePopupMenu' CreatePopupMenu user32 0 @@ -394,12 +409,15 @@ imp 'DestroyWindow' DestroyWindow user32 1 imp 'DispatchMessage' DispatchMessageW user32 1 imp 'DrawText' DrawTextW user32 5 imp 'DrawTextEx' DrawTextExW user32 6 +imp 'EmptyClipboard' EmptyClipboard user32 0 imp 'EndPaint' EndPaint user32 2 imp 'EnumChildWindows' EnumChildWindows user32 3 imp 'FillRect' FillRect user32 3 imp 'FindWindow' FindWindowW user32 2 imp 'FindWindowEx' FindWindowExW user32 4 +imp 'GetAsyncKeyState' GetAsyncKeyState user32 1 imp 'GetClientRect' GetClientRect user32 2 +imp 'GetClipboardData' GetClipboardData user32 1 imp 'GetCursor' GetCursor user32 0 imp 'GetCursorPos' GetCursorPos user32 1 imp 'GetDC' GetDC user32 1 @@ -408,9 +426,12 @@ imp 'GetKeyState' GetKeyState user32 1 imp 'GetKeyboardLayout' GetKeyboardLayout user32 1 imp 'GetMenu' GetMenu user32 1 imp 'GetMessage' GetMessageW user32 4 +imp 'GetMonitorInfo' GetMonitorInfoW user32 2 +imp 'GetRawInputData' GetRawInputData user32 5 imp 'GetParent' GetParent user32 1 imp 'GetShellWindow' GetShellWindow user32 0 imp 'GetSystemMenu' GetSystemMenu user32 2 +imp 'GetSystemMetrics' GetSystemMetrics user32 1 imp 'GetWindow' GetWindow user32 2 imp 'GetWindowPlacement' GetWindowPlacement user32 2 imp 'GetWindowRect' GetWindowRect user32 2 @@ -431,13 +452,22 @@ imp 'MapVirtualKeyEx' MapVirtualKeyExW user32 3 imp 'MessageBox' MessageBoxW user32 4 imp 'MessageBoxEx' MessageBoxExW user32 5 imp 'MoveWindow' MoveWindow user32 6 +imp 'MonitorFromPoint' MonitorFromPoint user32 2 +imp 'MonitorFromWindow' MonitorFromWindow user32 2 +imp 'OpenClipboard' OpenClipboard user32 1 imp 'PeekMessage' PeekMessageW user32 5 +imp 'PostMessage' PostMessageW user32 4 imp 'PostQuitMessage' PostQuitMessage user32 1 +imp 'PtInRect' PtInRect user32 2 imp 'RedrawWindow' RedrawWindow user32 4 imp 'RegisterClass' RegisterClassW user32 1 imp 'RegisterClassEx' RegisterClassExW user32 1 +imp 'RegisterRawInputDevices' RegisterRawInputDevices user32 3 imp 'ReleaseCapture' ReleaseCapture user32 0 imp 'ReleaseDC' ReleaseDC user32 2 +imp 'ScreenToClient' ScreenToClient user32 2 +imp 'SetClipboardData' SetClipboardData user32 2 +imp 'SetCursorPos' SetCursorPos user32 2 imp 'SendMessage' SendMessageW user32 4 imp 'SetCapture' SetCapture user32 1 imp 'SetClassLong' SetClassLongW user32 3 @@ -445,6 +475,7 @@ imp 'SetCursor' SetCursor user32 1 imp 'SetParent' SetParent user32 2 imp 'SetTimer' SetTimer user32 4 imp 'SetWindowLong' SetWindowLongW user32 3 +imp 'SetWindowLongPtr' SetWindowLongPtrW user32 3 imp 'SetWindowPlacement' SetWindowPlacement user32 2 imp 'SetWindowPos' SetWindowPos user32 7 imp 'SetWindowText' SetWindowTextW user32 2 @@ -453,12 +484,23 @@ imp 'SetWindowsHookEx' SetWindowsHookExW user32 4 imp 'ShowCaret' ShowCaret user32 1 imp 'ShowCursor' ShowCursor user32 1 imp 'ShowWindow' ShowWindow user32 2 +imp 'TrackMouseEvent' TrackMouseEvent user32 1 imp 'TrackPopupMenu' TrackPopupMenu user32 7 imp 'TranslateMessage' TranslateMessage user32 1 imp 'UnhookWindowsHook' UnhookWindowsHook user32 2 imp 'UnhookWindowsHookEx' UnhookWindowsHookEx user32 1 +imp 'UnregisterClass' UnregisterClassW user32 2 imp 'UpdateWindow' UpdateWindow user32 1 imp 'WaitForInputIdle' WaitForInputIdle user32 2 +imp 'WindowFromPoint' WindowFromPoint user32 1 + +# SHELL32.DLL +# +# Name Actual DLL Arity +imp 'CommandLineToArgv' CommandLineToArgvW shell32 2 +imp 'DragAcceptFiles' DragAcceptFiles shell32 2 +imp 'DragFinish' DragFinish shell32 1 +imp 'DragQueryFile' DragQueryFileW shell32 4 # GDI32.DLL # @@ -472,6 +514,7 @@ imp 'CreateDIBSection' CreateDIBSection gdi32 6 imp 'CreateRectRgn' CreateRectRgn gdi32 4 imp 'DeleteDC' DeleteDC gdi32 1 imp 'DeleteObject' DeleteObject gdi32 1 +imp 'DescribePixelFormat' DescribePixelFormat gdi32 4 imp 'GetPixel' GetPixel gdi32 3 imp 'RestoreDC' RestoreDC gdi32 2 imp 'SaveDC' SaveDC gdi32 1 @@ -716,6 +759,7 @@ imp 'NtQuerySecurityObject' NtQuerySecurityObject ntdll 5 imp 'NtQuerySymbolicLinkObject' NtQuerySymbolicLinkObject ntdll 3 imp 'NtQuerySystemInformation' NtQuerySystemInformation ntdll 4 imp 'NtQuerySystemTime' NtQuerySystemTime ntdll 1 +imp 'NtQueryTimerResolution' NtQueryTimerResolution ntdll 3 imp 'NtQueryValueKey' NtQueryValueKey ntdll 6 imp 'NtQueryVirtualMemory' NtQueryVirtualMemory ntdll 6 imp 'NtQueryVolumeInformationFile' NtQueryVolumeInformationFile ntdll 5 @@ -732,6 +776,7 @@ imp 'NtSetInformationFile' NtSetInformationFile ntdll 5 imp 'NtSetInformationThread' NtSetInformationThread ntdll 4 imp 'NtSetIntervalProfile' NtSetIntervalProfile ntdll 2 imp 'NtSetTimer' NtSetTimer ntdll 7 +imp 'NtSetTimerResolution' NtSetTimerResolution ntdll 3 imp 'NtSetValueKey' NtSetValueKey ntdll 6 imp 'NtSignalAndWaitForSingleObject' NtSignalAndWaitForSingleObject ntdll 4 imp 'NtStartProfile' NtStartProfile ntdll 1 diff --git a/libc/nt/memory.h b/libc/nt/memory.h index 376f0fb16..9f6792657 100644 --- a/libc/nt/memory.h +++ b/libc/nt/memory.h @@ -71,8 +71,17 @@ bool32 VirtualUnlock(const void *lpAddress, size_t dwSize); uint64_t VirtualQuery(const void *lpAddress, struct NtMemoryBasicInformation *lpBuffer, uint64_t dwLength); +uint64_t VirtualQueryEx(int64_t hProcess, const void *lpAddress, + struct NtMemoryBasicInformation *lpBuffer, + uint64_t dwLength); + void *VirtualAllocEx(int64_t hProcess, void *lpAddress, uint64_t dwSize, uint32_t flAllocationType, uint32_t flProtect); +bool32 VirtualProtectEx(int64_t hProcess, void *lpAddress, uint64_t dwSize, + uint32_t flNewProtect, uint32_t *out_lpflOldProtect); +bool32 WriteProcessMemory(int64_t hProcess, void *lpBaseAddress, + const void *lpBuffer, uint64_t nSize, + uint64_t *opt_out_lpNumberOfBytesWritten); int64_t GetProcessHeap(void); void *HeapAlloc(int64_t hHeap, uint32_t dwFlags, size_t dwBytes) __wur; diff --git a/libc/nt/nls.h b/libc/nt/nls.h new file mode 100644 index 000000000..4e2761519 --- /dev/null +++ b/libc/nt/nls.h @@ -0,0 +1,35 @@ +#ifndef COSMOPOLITAN_LIBC_NT_NLS_H_ +#define COSMOPOLITAN_LIBC_NT_NLS_H_ +#include "libc/nt/struct/cpinfoex.h" +/* ░░░░ + ▒▒▒░░░▒▒▒▒▒▒▒▓▓▓░ + ▒▒▒▒░░░▒▒▒▒▒▒▓▓▓▓▓▓░ + ▒▒▒▒░░░▒▒▒▒▒▒▒▓▓▓▓▓▓ ▒▓░ + ▒▒▒░░░░▒▒▒▒▒▒▓▓▓▓▓▓ ▓▓▓▓▓▓▒ ▒▒▒▓▓█ + ▒▒▒▒░░░▒▒▒▒▒▒▒▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▓▓▓ + ░▒▒▒░░░░▒▒▒▒▒▒▓▓▓▓▓▓ █▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▓▓█ + ▒▒▒▒░░░▒▒▒▒▒▒▒▓▓▓▓▓░ ▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▓▓▓ + ▒▒▒▒░░░▒▒▒▒▒▒▒▓▓▓▓▓▓ ▒▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▓▓▒ + ▒▒▒▒▓▓ ▓▒▒▓▓▓▓ ▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▓▓█ + ▒▓ ▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▓▓ + ░░░░░░░░░░░▒▒▒▒ ▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▓▓█ + ▒▒░░░░░░░░░░▒▒▒▒▒▓▓▓ ▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▓▓▓ + ░▒░░░░░░░░░░░▒▒▒▒▒▓▓ ▓░ ░▓███▓ + ▒▒░░░░░░░░░░▒▒▒▒▒▓▓░ ▒▓▓▓▒▒▒ ░▒▒▒▓ ████████████ + ▒▒░░░░░░░░░░░▒▒▒▒▒▓▓ ▒▓▓▓▓▒▒▒▒▒▒▒▒░░░▒▒▒▒▒░ ░███ + ▒░░░░░░░░░░░▒▒▒▒▒▓▓ ▓▓▓▓▒▒▒▒▒▒▒▒░░░░▒▒▒▒▓ ███ + ▒▒░░░░░░░░░░▒▒▒▒▒▒▓▓ ▒▓▓▓▒▒▒▒▒▒▒▒░░░░▒▒▒▒▒ ▓██ + ▒░░░░░░░░░░░▒▒▒▒▒▓▓ ▓▓▓▓▒▒▒▒▒▒▒▒░░░▒▒▒▒▒▓ ▓██ + ▒▒░░░▒▒▒░░░▒▒░▒▒▒▓▓▒ ▒▓▓▓▒▒▒▒▒▒▒▒░░░░▒▒▒▒▒ ███ + ░▒▓ ░▓▓▓▓▒▒▒▒▒▒▒▒░░░░▒▒▒▒▓ ▓██ +╔────────────────────────────────────────────────────────────────▀▀▀─────────│─╗ +│ cosmopolitan § new technology » internationalization ─╬─│┼ +╚────────────────────────────────────────────────────────────────────────────│*/ +COSMOPOLITAN_C_START_ + +uint32_t GetOEMCP(); +uint32_t GetACP(); +bool32 GetCPInfoEx(uint32_t CodePage, uint32_t dwFlags, struct NtCpInfoEx *out_lpCPInfoEx) paramsnonnull((3)); + +COSMOPOLITAN_C_END_ +#endif /* COSMOPOLITAN_LIBC_NT_NLS_H_ */ \ No newline at end of file diff --git a/libc/nt/ntdll.h b/libc/nt/ntdll.h index 04a8e60f3..f251b923a 100644 --- a/libc/nt/ntdll.h +++ b/libc/nt/ntdll.h @@ -224,6 +224,16 @@ NtStatus RtlUnlockHeap(int64_t heap); NtStatus RtlGetProcessHeaps(uint32_t count, void **out_Heaps); NtStatus RtlWalkHeap(int64_t heap, void *out_Info); +/*───────────────────────────────────────────────────────────────────────────│─╗ +│ cosmopolitan § new technology » beyond the pale » i am the time lorde ─╬─│┼ +╚────────────────────────────────────────────────────────────────────────────│*/ + +NtStatus NtSetTimerResolution(uint32_t DesiredResolution, bool32 SetResolution, + uint32_t *out_CurrentResolution); +NtStatus NtQueryTimerResolution(uint32_t *out_MinimumResolution, + uint32_t *out_MaximumResolution, + uint32_t *out_CurrentResolution); + #if ShouldUseMsabiAttribute() #include "libc/nt/thunk/ntdll.inc" #endif /* ShouldUseMsabiAttribute() */ diff --git a/libc/nt/ntdll/NtQueryTimerResolution.S b/libc/nt/ntdll/NtQueryTimerResolution.S new file mode 100644 index 000000000..2bb696be7 --- /dev/null +++ b/libc/nt/ntdll/NtQueryTimerResolution.S @@ -0,0 +1,18 @@ +#include "libc/nt/ntdllimport.h" +.ntimp NtQueryTimerResolution,NtQueryTimerResolution + + .text.windows + .ftrace1 +NtQueryTimerResolution: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_NtQueryTimerResolution(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn NtQueryTimerResolution,globl + .previous diff --git a/libc/nt/ntdll/NtSetTimerResolution.S b/libc/nt/ntdll/NtSetTimerResolution.S new file mode 100644 index 000000000..bbd707afe --- /dev/null +++ b/libc/nt/ntdll/NtSetTimerResolution.S @@ -0,0 +1,18 @@ +#include "libc/nt/ntdllimport.h" +.ntimp NtSetTimerResolution,NtSetTimerResolution + + .text.windows + .ftrace1 +NtSetTimerResolution: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_NtSetTimerResolution(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn NtSetTimerResolution,globl + .previous diff --git a/libc/nt/registry.h b/libc/nt/registry.h index d7f8abb99..a03abfc57 100644 --- a/libc/nt/registry.h +++ b/libc/nt/registry.h @@ -51,6 +51,8 @@ int RegOpenKey(int64_t hKey, const char16_t *opt_lpSubKey, int RegOpenKeyEx(int64_t hKey, const char16_t *opt_lpSubKey, uint32_t opt_ulOptions, int samDesired, int64_t *out_phkResult) paramsnonnull((5)); +int RegOpenKeyExA(int64_t hKey, const char *opt_lpSubKey, uint32_t opt_ulOptions, + int samDesired, int64_t *out_phkResult) paramsnonnull((5)); int RegCloseKey(int64_t hKey); int RegGetValue(int64_t hkey, const char16_t *opt_lpSubKey, diff --git a/libc/nt/shell32/CommandLineToArgvW.S b/libc/nt/shell32/CommandLineToArgvW.S new file mode 100644 index 000000000..ef2f71100 --- /dev/null +++ b/libc/nt/shell32/CommandLineToArgvW.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp shell32,__imp_CommandLineToArgvW,CommandLineToArgvW + + .text.windows + .ftrace1 +CommandLineToArgv: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_CommandLineToArgvW(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn CommandLineToArgv,globl + .previous diff --git a/libc/nt/shell32/DragAcceptFiles.S b/libc/nt/shell32/DragAcceptFiles.S new file mode 100644 index 000000000..2c3c78775 --- /dev/null +++ b/libc/nt/shell32/DragAcceptFiles.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp shell32,__imp_DragAcceptFiles,DragAcceptFiles + + .text.windows + .ftrace1 +DragAcceptFiles: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_DragAcceptFiles(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn DragAcceptFiles,globl + .previous diff --git a/libc/nt/shell32/DragFinish.S b/libc/nt/shell32/DragFinish.S new file mode 100644 index 000000000..5bb00758e --- /dev/null +++ b/libc/nt/shell32/DragFinish.S @@ -0,0 +1,20 @@ +#include "libc/nt/codegen.h" +.imp shell32,__imp_DragFinish,DragFinish + + .text.windows + .ftrace1 +DragFinish: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov %rdi,%rcx + sub $32,%rsp + call *__imp_DragFinish(%rip) + leave +#elif defined(__aarch64__) + mov x0,#0 +#endif + ret + .endfn DragFinish,globl + .previous diff --git a/libc/nt/shell32/DragQueryFileW.S b/libc/nt/shell32/DragQueryFileW.S new file mode 100644 index 000000000..efec6118d --- /dev/null +++ b/libc/nt/shell32/DragQueryFileW.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp shell32,__imp_DragQueryFileW,DragQueryFileW + + .text.windows + .ftrace1 +DragQueryFile: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_DragQueryFileW(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn DragQueryFile,globl + .previous diff --git a/libc/nt/struct/cpinfoex.h b/libc/nt/struct/cpinfoex.h new file mode 100644 index 000000000..754501bb5 --- /dev/null +++ b/libc/nt/struct/cpinfoex.h @@ -0,0 +1,13 @@ +#ifndef COSMOPOLITAN_LIBC_NT_STRUCT_CPINFOEX_H_ +#define COSMOPOLITAN_LIBC_NT_STRUCT_CPINFOEX_H_ + +struct NtCpInfoEx { + uint32_t MaxCharSize; + uint8_t DefaultChar[2]; + uint8_t LeadByte[12]; + char16_t UnicodeDefaultChar; + uint32_t CodePage; + char16_t CodePageName[260]; +}; + +#endif /* COSMOPOLITAN_LIBC_NT_STRUCT_CPINFOEX_H_ */ diff --git a/libc/nt/user32/AdjustWindowRectEx.S b/libc/nt/user32/AdjustWindowRectEx.S new file mode 100644 index 000000000..04416b211 --- /dev/null +++ b/libc/nt/user32/AdjustWindowRectEx.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_AdjustWindowRectEx,AdjustWindowRectEx + + .text.windows + .ftrace1 +AdjustWindowRectEx: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_AdjustWindowRectEx(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn AdjustWindowRectEx,globl + .previous diff --git a/libc/nt/user32/ClientToScreen.S b/libc/nt/user32/ClientToScreen.S new file mode 100644 index 000000000..9bc45dee8 --- /dev/null +++ b/libc/nt/user32/ClientToScreen.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_ClientToScreen,ClientToScreen + + .text.windows + .ftrace1 +ClientToScreen: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_ClientToScreen(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn ClientToScreen,globl + .previous diff --git a/libc/nt/user32/ClipCursor.S b/libc/nt/user32/ClipCursor.S new file mode 100644 index 000000000..f2c7f19ec --- /dev/null +++ b/libc/nt/user32/ClipCursor.S @@ -0,0 +1,20 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_ClipCursor,ClipCursor + + .text.windows + .ftrace1 +ClipCursor: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov %rdi,%rcx + sub $32,%rsp + call *__imp_ClipCursor(%rip) + leave +#elif defined(__aarch64__) + mov x0,#0 +#endif + ret + .endfn ClipCursor,globl + .previous diff --git a/libc/nt/user32/CloseClipboard.S b/libc/nt/user32/CloseClipboard.S new file mode 100644 index 000000000..b57e82022 --- /dev/null +++ b/libc/nt/user32/CloseClipboard.S @@ -0,0 +1,19 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_CloseClipboard,CloseClipboard + + .text.windows + .ftrace1 +CloseClipboard: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + sub $32,%rsp + call *__imp_CloseClipboard(%rip) + leave +#elif defined(__aarch64__) + mov x0,#0 +#endif + ret + .endfn CloseClipboard,globl + .previous diff --git a/libc/nt/user32/EmptyClipboard.S b/libc/nt/user32/EmptyClipboard.S new file mode 100644 index 000000000..6038d29da --- /dev/null +++ b/libc/nt/user32/EmptyClipboard.S @@ -0,0 +1,19 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_EmptyClipboard,EmptyClipboard + + .text.windows + .ftrace1 +EmptyClipboard: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + sub $32,%rsp + call *__imp_EmptyClipboard(%rip) + leave +#elif defined(__aarch64__) + mov x0,#0 +#endif + ret + .endfn EmptyClipboard,globl + .previous diff --git a/libc/nt/user32/GetAsyncKeyState.S b/libc/nt/user32/GetAsyncKeyState.S new file mode 100644 index 000000000..9b1d32e1d --- /dev/null +++ b/libc/nt/user32/GetAsyncKeyState.S @@ -0,0 +1,20 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_GetAsyncKeyState,GetAsyncKeyState + + .text.windows + .ftrace1 +GetAsyncKeyState: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov %rdi,%rcx + sub $32,%rsp + call *__imp_GetAsyncKeyState(%rip) + leave +#elif defined(__aarch64__) + mov x0,#0 +#endif + ret + .endfn GetAsyncKeyState,globl + .previous diff --git a/libc/nt/user32/GetClipboardData.S b/libc/nt/user32/GetClipboardData.S new file mode 100644 index 000000000..b51af7d3a --- /dev/null +++ b/libc/nt/user32/GetClipboardData.S @@ -0,0 +1,20 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_GetClipboardData,GetClipboardData + + .text.windows + .ftrace1 +GetClipboardData: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov %rdi,%rcx + sub $32,%rsp + call *__imp_GetClipboardData(%rip) + leave +#elif defined(__aarch64__) + mov x0,#0 +#endif + ret + .endfn GetClipboardData,globl + .previous diff --git a/libc/nt/user32/GetMonitorInfoW.S b/libc/nt/user32/GetMonitorInfoW.S new file mode 100644 index 000000000..2ec6986fe --- /dev/null +++ b/libc/nt/user32/GetMonitorInfoW.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_GetMonitorInfoW,GetMonitorInfoW + + .text.windows + .ftrace1 +GetMonitorInfo: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_GetMonitorInfoW(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn GetMonitorInfo,globl + .previous diff --git a/libc/nt/user32/GetRawInputData.S b/libc/nt/user32/GetRawInputData.S new file mode 100644 index 000000000..6324125ea --- /dev/null +++ b/libc/nt/user32/GetRawInputData.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_GetRawInputData,GetRawInputData + + .text.windows + .ftrace1 +GetRawInputData: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_GetRawInputData(%rip),%rax + jmp __sysv2nt6 +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn GetRawInputData,globl + .previous diff --git a/libc/nt/user32/GetSystemMetrics.S b/libc/nt/user32/GetSystemMetrics.S new file mode 100644 index 000000000..4a92d3184 --- /dev/null +++ b/libc/nt/user32/GetSystemMetrics.S @@ -0,0 +1,20 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_GetSystemMetrics,GetSystemMetrics + + .text.windows + .ftrace1 +GetSystemMetrics: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov %rdi,%rcx + sub $32,%rsp + call *__imp_GetSystemMetrics(%rip) + leave +#elif defined(__aarch64__) + mov x0,#0 +#endif + ret + .endfn GetSystemMetrics,globl + .previous diff --git a/libc/nt/user32/MonitorFromPoint.S b/libc/nt/user32/MonitorFromPoint.S new file mode 100644 index 000000000..b27b7a8aa --- /dev/null +++ b/libc/nt/user32/MonitorFromPoint.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_MonitorFromPoint,MonitorFromPoint + + .text.windows + .ftrace1 +MonitorFromPoint: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_MonitorFromPoint(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn MonitorFromPoint,globl + .previous diff --git a/libc/nt/user32/MonitorFromWindow.S b/libc/nt/user32/MonitorFromWindow.S new file mode 100644 index 000000000..ec49593a4 --- /dev/null +++ b/libc/nt/user32/MonitorFromWindow.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_MonitorFromWindow,MonitorFromWindow + + .text.windows + .ftrace1 +MonitorFromWindow: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_MonitorFromWindow(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn MonitorFromWindow,globl + .previous diff --git a/libc/nt/user32/OpenClipboard.S b/libc/nt/user32/OpenClipboard.S new file mode 100644 index 000000000..f6b8afb7e --- /dev/null +++ b/libc/nt/user32/OpenClipboard.S @@ -0,0 +1,20 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_OpenClipboard,OpenClipboard + + .text.windows + .ftrace1 +OpenClipboard: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov %rdi,%rcx + sub $32,%rsp + call *__imp_OpenClipboard(%rip) + leave +#elif defined(__aarch64__) + mov x0,#0 +#endif + ret + .endfn OpenClipboard,globl + .previous diff --git a/libc/nt/user32/PostMessageW.S b/libc/nt/user32/PostMessageW.S new file mode 100644 index 000000000..5da8cf132 --- /dev/null +++ b/libc/nt/user32/PostMessageW.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_PostMessageW,PostMessageW + + .text.windows + .ftrace1 +PostMessage: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_PostMessageW(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn PostMessage,globl + .previous diff --git a/libc/nt/user32/PtInRect.S b/libc/nt/user32/PtInRect.S new file mode 100644 index 000000000..3a3ae8c38 --- /dev/null +++ b/libc/nt/user32/PtInRect.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_PtInRect,PtInRect + + .text.windows + .ftrace1 +PtInRect: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_PtInRect(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn PtInRect,globl + .previous diff --git a/libc/nt/user32/RegisterRawInputDevices.S b/libc/nt/user32/RegisterRawInputDevices.S new file mode 100644 index 000000000..9f100d244 --- /dev/null +++ b/libc/nt/user32/RegisterRawInputDevices.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_RegisterRawInputDevices,RegisterRawInputDevices + + .text.windows + .ftrace1 +RegisterRawInputDevices: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_RegisterRawInputDevices(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn RegisterRawInputDevices,globl + .previous diff --git a/libc/nt/user32/ScreenToClient.S b/libc/nt/user32/ScreenToClient.S new file mode 100644 index 000000000..3c1d2ca54 --- /dev/null +++ b/libc/nt/user32/ScreenToClient.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_ScreenToClient,ScreenToClient + + .text.windows + .ftrace1 +ScreenToClient: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_ScreenToClient(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn ScreenToClient,globl + .previous diff --git a/libc/nt/user32/SetClipboardData.S b/libc/nt/user32/SetClipboardData.S new file mode 100644 index 000000000..ca8e59591 --- /dev/null +++ b/libc/nt/user32/SetClipboardData.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_SetClipboardData,SetClipboardData + + .text.windows + .ftrace1 +SetClipboardData: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_SetClipboardData(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn SetClipboardData,globl + .previous diff --git a/libc/nt/user32/SetCursorPos.S b/libc/nt/user32/SetCursorPos.S new file mode 100644 index 000000000..f29847884 --- /dev/null +++ b/libc/nt/user32/SetCursorPos.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_SetCursorPos,SetCursorPos + + .text.windows + .ftrace1 +SetCursorPos: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_SetCursorPos(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn SetCursorPos,globl + .previous diff --git a/libc/nt/user32/SetWindowLongPtrW.S b/libc/nt/user32/SetWindowLongPtrW.S new file mode 100644 index 000000000..3ff044c05 --- /dev/null +++ b/libc/nt/user32/SetWindowLongPtrW.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_SetWindowLongPtrW,SetWindowLongPtrW + + .text.windows + .ftrace1 +SetWindowLongPtr: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_SetWindowLongPtrW(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn SetWindowLongPtr,globl + .previous diff --git a/libc/nt/user32/TrackMouseEvent.S b/libc/nt/user32/TrackMouseEvent.S new file mode 100644 index 000000000..7220d178a --- /dev/null +++ b/libc/nt/user32/TrackMouseEvent.S @@ -0,0 +1,20 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_TrackMouseEvent,TrackMouseEvent + + .text.windows + .ftrace1 +TrackMouseEvent: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov %rdi,%rcx + sub $32,%rsp + call *__imp_TrackMouseEvent(%rip) + leave +#elif defined(__aarch64__) + mov x0,#0 +#endif + ret + .endfn TrackMouseEvent,globl + .previous diff --git a/libc/nt/user32/UnregisterClassW.S b/libc/nt/user32/UnregisterClassW.S new file mode 100644 index 000000000..398af1b53 --- /dev/null +++ b/libc/nt/user32/UnregisterClassW.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_UnregisterClassW,UnregisterClassW + + .text.windows + .ftrace1 +UnregisterClass: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_UnregisterClassW(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn UnregisterClass,globl + .previous diff --git a/libc/nt/user32/WindowFromPoint.S b/libc/nt/user32/WindowFromPoint.S new file mode 100644 index 000000000..eaf6c0410 --- /dev/null +++ b/libc/nt/user32/WindowFromPoint.S @@ -0,0 +1,20 @@ +#include "libc/nt/codegen.h" +.imp user32,__imp_WindowFromPoint,WindowFromPoint + + .text.windows + .ftrace1 +WindowFromPoint: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov %rdi,%rcx + sub $32,%rsp + call *__imp_WindowFromPoint(%rip) + leave +#elif defined(__aarch64__) + mov x0,#0 +#endif + ret + .endfn WindowFromPoint,globl + .previous diff --git a/libc/proc/BUILD.mk b/libc/proc/BUILD.mk index 1ddefad2b..8491e5635 100644 --- a/libc/proc/BUILD.mk +++ b/libc/proc/BUILD.mk @@ -30,11 +30,14 @@ LIBC_PROC_A_DIRECTDEPS = \ LIBC_MEM \ LIBC_NEXGEN32E \ LIBC_NT_KERNEL32 \ + LIBC_NT_NTDLL \ LIBC_NT_PSAPI \ LIBC_RUNTIME \ LIBC_STR \ LIBC_SYSV \ LIBC_SYSV_CALLS \ + THIRD_PARTY_DLMALLOC \ + THIRD_PARTY_GDTOA \ THIRD_PARTY_NSYNC \ LIBC_PROC_A_DEPS := \ diff --git a/libc/proc/describefds.c b/libc/proc/describefds.c index 6cf25d78b..4bc203fe3 100644 --- a/libc/proc/describefds.c +++ b/libc/proc/describefds.c @@ -22,7 +22,6 @@ #include "libc/fmt/itoa.h" #include "libc/intrin/fds.h" #include "libc/intrin/maps.h" -#include "libc/intrin/strace.h" #include "libc/mem/mem.h" #include "libc/nt/files.h" #include "libc/nt/runtime.h" diff --git a/libc/proc/execve-nt.greg.c b/libc/proc/execve-nt.greg.c index fbb13bd46..42cb01c67 100644 --- a/libc/proc/execve-nt.greg.c +++ b/libc/proc/execve-nt.greg.c @@ -17,53 +17,89 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/atomic.h" +#include "libc/calls/calls.h" #include "libc/calls/internal.h" +#include "libc/calls/sig.internal.h" #include "libc/calls/struct/sigset.internal.h" #include "libc/calls/syscall-nt.internal.h" #include "libc/errno.h" #include "libc/fmt/itoa.h" +#include "libc/intrin/dll.h" #include "libc/intrin/fds.h" #include "libc/intrin/kprintf.h" +#include "libc/intrin/strace.h" #include "libc/mem/mem.h" +#include "libc/nt/accounting.h" #include "libc/nt/enum/processaccess.h" #include "libc/nt/enum/startf.h" +#include "libc/nt/enum/status.h" #include "libc/nt/errors.h" #include "libc/nt/files.h" #include "libc/nt/process.h" #include "libc/nt/runtime.h" #include "libc/nt/struct/processinformation.h" #include "libc/nt/struct/startupinfo.h" +#include "libc/nt/synchronization.h" +#include "libc/nt/thread.h" +#include "libc/nt/thunk/msabi.h" #include "libc/proc/describefds.internal.h" #include "libc/proc/ntspawn.h" +#include "libc/runtime/internal.h" #include "libc/str/str.h" #include "libc/sysv/consts/at.h" #include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/sig.h" #include "libc/sysv/errfuns.h" #include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" #ifdef __x86_64__ +__msabi extern typeof(CloseHandle) *const __imp_CloseHandle; +__msabi extern typeof(TerminateProcess) *const __imp_TerminateProcess; + +extern atomic_int __sig_worker_state; + +static void sys_execve_nt_abort(sigset_t sigmask) { + __sig_worker_state &= ~2; + __sig_unblock(sigmask); +} + textwindows int sys_execve_nt(const char *program, char *const argv[], char *const envp[]) { // execve() needs to be @asyncsignalsafe sigset_t sigmask = __sig_block(); - _pthread_lock(); + __sig_worker_state |= 2; + for (;;) + if (__sig_worker_state & 1) + break; // new process should be a child of our parent - int64_t hParentProcess; - int ppid = sys_getppid_nt(); - if (!(hParentProcess = OpenProcess( - kNtProcessDupHandle | kNtProcessCreateProcess, false, ppid))) { - _pthread_unlock(); - __sig_unblock(sigmask); - return -1; - } + int64_t hParentProcess = + sys_getppid_nt_win32 + ? OpenProcess(kNtProcessDupHandle | kNtProcessCreateProcess, false, + sys_getppid_nt_win32) + : 0; + + // inherit pid + char pidvar[11 + 21]; + FormatUint64(stpcpy(pidvar, "_COSMO_PID="), __pid); // inherit signal mask char maskvar[6 + 21]; FormatUint64(stpcpy(maskvar, "_MASK="), sigmask); + // inherit parent process id + char ppidvar[12 + 21 + 1 + 21 + 1], *p = ppidvar; + p = stpcpy(p, "_COSMO_PPID="); + if (hParentProcess) { + p = FormatUint64(p, sys_getppid_nt_win32); + *p++ = ':'; + p = FormatUint64(p, __pid); + setenv("_COSMO_PPID", ppidvar, true); + } + // define stdio handles for the spawned subprocess struct NtStartupInfo si = { .cb = sizeof(struct NtStartupInfo), @@ -77,29 +113,44 @@ textwindows int sys_execve_nt(const char *program, char *const argv[], } } + // which process is responsible for spawning the child? + int64_t hCreatorProcess; + if (hParentProcess) { + hCreatorProcess = hParentProcess; + } else { + hCreatorProcess = GetCurrentProcess(); + } + // pass serialized file descriptor table in environment char *fdspec; int64_t *lpExplicitHandles; uint32_t dwExplicitHandleCount; - if (!(fdspec = __describe_fds(g_fds.p, g_fds.n, &si, hParentProcess, + if (!(fdspec = __describe_fds(g_fds.p, g_fds.n, &si, hCreatorProcess, &lpExplicitHandles, &dwExplicitHandleCount))) { - CloseHandle(hParentProcess); - _pthread_unlock(); - __sig_unblock(sigmask); + if (hParentProcess) + __imp_CloseHandle(hParentProcess); + sys_execve_nt_abort(sigmask); return -1; } + // inherit pending signals + atomic_fetch_or_explicit( + __sig.process, + atomic_load_explicit(&__get_tls()->tib_sigpending, memory_order_acquire), + memory_order_release); + // launch the process struct NtProcessInformation pi; int rc = ntspawn(&(struct NtSpawnArgs){ - AT_FDCWD, program, argv, envp, (char *[]){fdspec, maskvar, 0}, 0, 0, - hParentProcess, lpExplicitHandles, dwExplicitHandleCount, &si, &pi}); - __undescribe_fds(hParentProcess, lpExplicitHandles, dwExplicitHandleCount); + AT_FDCWD, program, argv, envp, + (char *[]){fdspec, maskvar, pidvar, ppidvar, 0}, 0, 0, hCreatorProcess, + lpExplicitHandles, dwExplicitHandleCount, &si, &pi}); + __undescribe_fds(hCreatorProcess, lpExplicitHandles, dwExplicitHandleCount); if (rc == -1) { free(fdspec); - CloseHandle(hParentProcess); - _pthread_unlock(); - __sig_unblock(sigmask); + if (hParentProcess) + __imp_CloseHandle(hParentProcess); + sys_execve_nt_abort(sigmask); if (GetLastError() == kNtErrorSharingViolation) { return etxtbsy(); } else { @@ -107,16 +158,55 @@ textwindows int sys_execve_nt(const char *program, char *const argv[], } } - // give child to libc/proc/proc.c worker thread in parent - int64_t handle; - if (DuplicateHandle(GetCurrentProcess(), pi.hProcess, hParentProcess, &handle, - 0, false, kNtDuplicateSameAccess)) { - unassert(!(handle & 0xFFFFFFFFFF000000)); - TerminateThisProcess(0x23000000u | handle); - } else { - // TODO(jart): Why does `make loc` print this? - // kprintf("DuplicateHandle failed w/ %d\n", GetLastError()); - TerminateThisProcess(ECHILD); + // check if parent spoofing worked + if (hParentProcess) { + // give child to libc/proc/proc.c worker thread in parent + int64_t handle; + if (DuplicateHandle(GetCurrentProcess(), pi.hProcess, hParentProcess, + &handle, 0, false, kNtDuplicateSameAccess)) { + unassert(!(handle & 0xFFFFFFFFFF000000)); + __imp_TerminateProcess(-1, 0x23000000u | handle); + } else { + // TODO(jart): Why does `make loc` print this? + // kprintf("DuplicateHandle failed w/ %d\n", GetLastError()); + __imp_TerminateProcess(-1, ECHILD); + } + __builtin_unreachable(); + } + + // we couldn't reparent the new process + STRACE("warning: execve() lingering due to non-cosmo parent process"); + + // terminate other threads + _pthread_lock(); + struct Dll *e; + struct PosixThread *me = _pthread_self(); + for (e = dll_first(_pthread_list); e; e = dll_next(_pthread_list, e)) { + struct PosixThread *pt = POSIXTHREAD_CONTAINER(e); + if (pt == me) + continue; + TerminateThread( + atomic_load_explicit(&pt->tib->tib_syshand, memory_order_relaxed), + SIGKILL); + } + + // wait for child to terminate and propagate exit code + for (;;) { + uint32_t status; + WaitForSingleObject(pi.hProcess, -1u); + GetExitCodeProcess(pi.hProcess, &status); + if (status != kNtStillActive) { + if ((status & 0xFF000000u) == 0x23000000u) { + // handle child execve() + __imp_CloseHandle(pi.hProcess); + pi.hProcess = status & 0x00FFFFFF; + } else { + // handle child _Exit() + if (status == 0xc9af3d51u) + status = kNtStillActive; + TerminateThisProcess(status); + } + } } } diff --git a/libc/proc/execve.c b/libc/proc/execve.c index 781bd3f26..b610f8b29 100644 --- a/libc/proc/execve.c +++ b/libc/proc/execve.c @@ -36,14 +36,55 @@ /** * Replaces current process with program. * + * Your `prog` may be an actually portable executable or a platform + * native binary (e.g. ELF, Mach-O, PE). On UNIX systems, your execve + * implementation will try to find where the `ape` interpreter program + * is installed on your system. The preferred location is `/usr/bin/ape` + * except on Apple Silicon where it's `/usr/local/bin/ape`. The $TMPDIR + * and $HOME locations that the APE shell script extracts the versioned + * ape binaries to will also be checked as a fallback path. Finally, if + * `prog` isn't an executable in any recognizable format, cosmo assumes + * it's a bourne shell script and launches it under /bin/sh. + * + * The signal mask and pending signals are inherited by the new process. + * Note the NetBSD kernel has a bug where pending signals are cleared. + * + * File descriptors that haven't been marked `O_CLOEXEC` through various + * devices such as open() and fcntl() will be inherited by the executed + * subprocess. The current file position of the duplicated descriptors + * is shared across processes. On Windows, `prog` needs to be built by + * cosmocc in order to properly inherit file descriptors. If a program + * compiled by MSVC or Cygwin is launched instead, then only the stdio + * file descriptors can be passed along. + * * On Windows, `argv` and `envp` can't contain binary strings. They need * to be valid UTF-8 in order to round-trip the WIN32 API, without being * corrupted. * - * On Windows, only file descriptors 0, 1 and 2 can be passed to a child - * process in such a way that allows them to be automatically discovered - * when the child process initializes. Cosmpolitan currently treats your - * other file descriptors as implicitly O_CLOEXEC. + * On Windows, cosmo execve uses parent spoofing to implement the UNIX + * behavior of replacing the current process. Since POSIX.1 also needs + * us to maintain the same PID number too, the _COSMO_PID environemnt + * variable is passed to the child process which specifies a spoofed + * PID. Whatever is in that variable will be reported by getpid() and + * other cosmo processes will be able to send signals to the process + * using that pid, via kill(). These synthetic PIDs which are only + * created by execve could potentially overlap with OS assignments if + * Windows recycles them. Cosmo avoids that by tracking handles of + * subprocesses. Each process has its own process manager thread, to + * associate pids with win32 handles, and execve will tell the parent + * process its new handle when it changes. However it's not perfect. + * There's still situations where processes created by execve() can + * cause surprising things to happen. For an alternative, consider + * posix_spawn() which is fastest and awesomest across all OSes. + * + * On Windows, support is currently not implemented for inheriting + * setitimer() and alarm() into an executed process. + * + * On Windows, support is currently not implemented for inheriting + * getrusage() statistics into an executed process. + * + * The executed process will share the same terminal and current + * directory. * * @param program will not be PATH searched, see commandv() * @param argv[0] is the name of the program to run diff --git a/libc/proc/fork-nt.c b/libc/proc/fork-nt.c index 996aff0fa..7f0ddca2d 100644 --- a/libc/proc/fork-nt.c +++ b/libc/proc/fork-nt.c @@ -16,63 +16,55 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "ape/sections.internal.h" -#include "libc/assert.h" -#include "libc/atomic.h" #include "libc/calls/internal.h" #include "libc/calls/sig.internal.h" #include "libc/calls/state.internal.h" #include "libc/calls/syscall_support-nt.internal.h" #include "libc/errno.h" -#include "libc/fmt/itoa.h" -#include "libc/intrin/atomic.h" #include "libc/intrin/directmap.h" +#include "libc/intrin/dll.h" #include "libc/intrin/kprintf.h" #include "libc/intrin/maps.h" #include "libc/intrin/strace.h" -#include "libc/intrin/tree.h" #include "libc/intrin/weaken.h" +#include "libc/limits.h" #include "libc/macros.h" -#include "libc/nt/createfile.h" -#include "libc/nt/enum/accessmask.h" #include "libc/nt/enum/creationdisposition.h" #include "libc/nt/enum/filemapflags.h" +#include "libc/nt/enum/memflags.h" #include "libc/nt/enum/pageflags.h" +#include "libc/nt/enum/processcreationflags.h" #include "libc/nt/enum/startf.h" #include "libc/nt/errors.h" -#include "libc/nt/ipc.h" #include "libc/nt/memory.h" #include "libc/nt/process.h" #include "libc/nt/runtime.h" -#include "libc/nt/signals.h" -#include "libc/nt/struct/ntexceptionpointers.h" +#include "libc/nt/struct/processinformation.h" +#include "libc/nt/struct/startupinfo.h" #include "libc/nt/thread.h" #include "libc/nt/thunk/msabi.h" -#include "libc/proc/ntspawn.h" -#include "libc/proc/proc.internal.h" +#include "libc/nt/winsock.h" +#include "libc/proc/proc.h" #include "libc/runtime/internal.h" -#include "libc/runtime/memtrack.internal.h" #include "libc/runtime/runtime.h" -#include "libc/runtime/stack.h" #include "libc/runtime/symbols.internal.h" -#include "libc/str/str.h" -#include "libc/sysv/consts/at.h" -#include "libc/sysv/consts/limits.h" #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/prot.h" #include "libc/sysv/consts/sig.h" #include "libc/sysv/errfuns.h" -#include "libc/thread/itimer.internal.h" -#include "libc/thread/posixthread.internal.h" #include "libc/thread/tls.h" -#include "libc/thread/tls2.internal.h" #ifdef __x86_64__ extern long __klog_handle; -void WipeKeystrokes(void); -__msabi extern typeof(GetCurrentProcessId) *const __imp_GetCurrentProcessId; +extern bool __winmain_isfork; +extern intptr_t __winmain_jmpbuf[5]; +extern struct CosmoTib *__winmain_tib; -static textwindows wontreturn void AbortFork(const char *func, void *addr) { +__msabi extern typeof(TlsAlloc) *const __imp_TlsAlloc; +__msabi extern typeof(MapViewOfFileEx) *const __imp_MapViewOfFileEx; +__msabi extern typeof(VirtualProtectEx) *const __imp_VirtualProtectEx; + +textwindows wontreturn static void AbortFork(const char *func, void *addr) { #if SYSDEBUG kprintf("fork() %!s(%lx) failed with win32 error %u\n", func, addr, GetLastError()); @@ -80,95 +72,10 @@ static textwindows wontreturn void AbortFork(const char *func, void *addr) { TerminateThisProcess(SIGSTKFLT); } -static textwindows char16_t *ParseInt(char16_t *p, int64_t *x) { - *x = 0; - while (*p == ' ') - p++; - while ('0' <= *p && *p <= '9') { - *x *= 10; - *x += *p++ - '0'; - } - return p; -} - -static inline textwindows ssize_t ForkIo(int64_t h, char *p, size_t n, - bool32 (*f)(int64_t, void *, uint32_t, - uint32_t *, - struct NtOverlapped *)) { - size_t i; - uint32_t x; - for (i = 0; i < n; i += x) { - if (!f(h, p + i, n - i, &x, 0)) - return __winerr(); - if (!x) - break; - } - return i; -} - -static dontinline textwindows ssize_t ForkIo2( - int64_t h, void *buf, size_t n, - bool32 (*fn)(int64_t, void *, uint32_t, uint32_t *, struct NtOverlapped *), - const char *sf, bool ischild) { - ssize_t rc = ForkIo(h, buf, n, fn); - if (ischild) { - // prevent crashes - __tls_enabled = false; - __pid = __imp_GetCurrentProcessId(); - __klog_handle = 0; - __maps.maps = 0; - } - NTTRACE("%s(%ld, %p, %'zu) → %'zd% m", sf, h, buf, n, rc); - return rc; -} - -static dontinline textwindows bool WriteAll(int64_t h, void *buf, size_t n) { - bool ok; - ok = ForkIo2(h, buf, n, (void *)WriteFile, "WriteFile", false) != -1; - if (!ok) { - STRACE("fork() failed in parent due to WriteAll(%ld, %p, %'zu) → %u", h, - buf, n, GetLastError()); - __print_maps(0); - } - return ok; -} - -static textwindows dontinline void ReadOrDie(int64_t h, void *buf, size_t n) { - ssize_t got; - if ((got = ForkIo2(h, buf, n, ReadFile, "ReadFile", true)) == -1) - AbortFork("ReadFile1", buf); - if (got != n) - AbortFork("ReadFile2", buf); -} - -static textwindows int64_t MapOrDie(uint32_t prot, uint64_t size) { - int64_t h; - for (;;) { - if ((h = CreateFileMapping(-1, 0, prot, size >> 32, size, 0))) - return h; - if (GetLastError() == kNtErrorAccessDenied) { - switch (prot) { - case kNtPageExecuteWritecopy: - prot = kNtPageWritecopy; - continue; - case kNtPageExecuteReadwrite: - prot = kNtPageReadwrite; - continue; - case kNtPageExecuteRead: - prot = kNtPageReadonly; - continue; - default: - break; - } - } - AbortFork("MapOrDie", (void *)size); - } -} - -static textwindows void ViewOrDie(int64_t h, uint32_t access, size_t pos, +textwindows static void ViewOrDie(int64_t h, uint32_t access, size_t pos, size_t size, void *base) { TryAgain: - if (!MapViewOfFileEx(h, access, pos >> 32, pos, size, base)) { + if (!__imp_MapViewOfFileEx(h, access, pos >> 32, pos, size, base)) { if ((access & kNtFileMapExecute) && GetLastError() == kNtErrorAccessDenied) { access &= ~kNtFileMapExecute; @@ -178,307 +85,237 @@ TryAgain: } } -static __msabi textwindows int OnForkCrash(struct NtExceptionPointers *ep) { - kprintf("error: fork() child crashed!%n" - "\tExceptionCode = %#x%n" - "\tRip = %x%n", - ep->ExceptionRecord->ExceptionCode, - ep->ContextRecord ? ep->ContextRecord->Rip : -1); - TerminateThisProcess(SIGSTKFLT); -} +textwindows static void sys_fork_nt_child(void) { -static textwindows void *Malloc(size_t size) { - return HeapAlloc(GetProcessHeap(), 0, size); -} + // setup runtime + __klog_handle = 0; + __tls_index = __imp_TlsAlloc(); + __morph_tls(); + __set_tls_win32(__winmain_tib); + __tls_enabled = true; -textwindows void WinMainForked(void) { - jmp_buf jb; - int64_t reader; - int64_t savetsc; - uint32_t varlen; - char16_t fvar[21 + 1 + 21 + 1]; - struct Fds *fds = __veil("r", &g_fds); + // resurrect shared memory mappings + struct Map *next; + for (struct Map *map = __maps_first(); map; map = next) { + next = __maps_next(map); - // check to see if the process was actually forked - // this variable should have the pipe handle numba - varlen = GetEnvironmentVariable(u"_FORK", fvar, ARRAYLEN(fvar)); - if (!varlen || varlen >= ARRAYLEN(fvar)) - return; - /* STRACE("WinMainForked()"); */ - SetEnvironmentVariable(u"_FORK", NULL); -#if SYSDEBUG - int64_t oncrash = AddVectoredExceptionHandler(1, (void *)OnForkCrash); -#endif - ParseInt(fvar, &reader); - - // read the cpu state from the parent process & plus - ReadOrDie(reader, jb, sizeof(jb)); - - // read memory mappings from parent process - struct Tree *maps = 0; - for (;;) { - struct Map *map = Malloc(sizeof(struct Map)); - ReadOrDie(reader, map, sizeof(struct Map)); - if (map->addr == MAP_FAILED) - break; - tree_insert(&maps, &map->tree, __maps_compare); - } - - // map memory into process - int granularity = __gransize; - for (struct Tree *e = tree_first(maps); e; e = tree_next(e)) { - struct Map *map = MAP_TREE_CONTAINER(e); - if ((uintptr_t)map->addr & (granularity - 1)) - continue; - // get true length in case mprotect() chopped up actual win32 map - size_t size = map->size; - for (struct Tree *e2 = tree_next(e); e2; e2 = tree_next(e2)) { - struct Map *map2 = MAP_TREE_CONTAINER(e2); - if (map2->hand == -1 && map->addr + size == map2->addr) { - size += map2->size; - } else { - break; + // cleanup nofork mappings + if (map->flags & MAP_NOFORK) { + if ((map->flags & MAP_TYPE) != MAP_FILE) { + tree_remove(&__maps.maps, &map->tree); + __maps.pages -= (map->size + __pagesize - 1) / __pagesize; + __maps.count -= 1; + __maps_free(map); } + continue; } - // obtain the most permissive access possible - unsigned prot, access; - if (map->readonlyfile) { - prot = kNtPageExecuteRead; - access = kNtFileMapRead | kNtFileMapExecute; - } else { - prot = kNtPageExecuteReadwrite; - access = kNtFileMapWrite | kNtFileMapExecute; - } + + // private maps already copied/protected to child by parent if ((map->flags & MAP_TYPE) != MAP_SHARED) { - // we don't need to close the map handle because sys_mmap_nt - // doesn't mark it inheritable across fork() for MAP_PRIVATE - map->hand = MapOrDie(prot, size); - ViewOrDie(map->hand, access, 0, size, map->addr); - ReadOrDie(reader, map->addr, size); - } else { - // we can however safely inherit MAP_SHARED with zero copy - ViewOrDie(map->hand, access, map->off, size, map->addr); + // it's not copy-on-write anymore + map->iscow = false; + // but it used VirtualAlloc() so munmap() must VirtualFree() + if (map->hand > 0) { + CloseHandle(map->hand); + map->hand = MAPS_VIRTUAL; + } + continue; } - } - // read the .data and .bss program image sections - savetsc = kStartTsc; - ReadOrDie(reader, __data_start, __data_end - __data_start); - ReadOrDie(reader, __bss_start, __bss_end - __bss_start); - kStartTsc = savetsc; - __tls_enabled = false; + // handle granularity aligned shared mapping + if (__maps_isalloc(map)) { - // fixup memory manager - __maps.maps = 0; - __maps.freed = 0; - __maps.count = 0; - __maps.pages = 0; - for (struct Tree *e = tree_first(maps); e; e = tree_next(e)) { - struct Map *map = MAP_TREE_CONTAINER(e); - __maps.count += 1; - __maps.pages += (map->size + __pagesize - 1) / __pagesize; + // get true size of win32 allocation + size_t allocsize = map->size; + for (struct Map *map2 = next; map2; map2 = __maps_next(map2)) { + if (!__maps_isalloc(map2) && map->addr + allocsize == map2->addr) { + allocsize += map2->size; + } else { + break; + } + } + + // create allocation with most permissive access possible + // if we don't create as rwx then we can't mprotect(rwx) later + unsigned access; + if (map->readonlyfile) { + access = kNtFileMapRead | kNtFileMapExecute; + } else { + access = kNtFileMapWrite | kNtFileMapExecute; + } + + // resurrect copyless memory via inherited win32 handle + ViewOrDie(map->hand, access, map->off, allocsize, map->addr); + } + + // restore memory protection status on pages unsigned old_protect; - if (!VirtualProtect(map->addr, map->size, __prot2nt(map->prot, map->iscow), - &old_protect)) - AbortFork("VirtualProtect", map->addr); + if (!__imp_VirtualProtectEx(GetCurrentProcess(), map->addr, map->size, + __prot2nt(map->prot, false), &old_protect)) + AbortFork("VirtualProtectEx", map->addr); } - __maps.maps = maps; - __maps_init(); - // mitosis complete - if (!CloseHandle(reader)) - AbortFork("CloseHandle", (void *)reader); + // function tracing is now safe + ftrace_enabled(+1); + + // initialize winsock + void WinSockFork(void); + if (_weaken(WinSockFork)) + _weaken(WinSockFork)(); // rewrap the stdin named pipe hack // since the handles closed on fork - fds->p[0].handle = GetStdHandle(kNtStdInputHandle); - fds->p[1].handle = GetStdHandle(kNtStdOutputHandle); - fds->p[2].handle = GetStdHandle(kNtStdErrorHandle); + g_fds.p[0].handle = GetStdHandle(kNtStdInputHandle); + g_fds.p[1].handle = GetStdHandle(kNtStdOutputHandle); + g_fds.p[2].handle = GetStdHandle(kNtStdErrorHandle); +} - // restore the crash reporting stuff -#if SYSDEBUG - RemoveVectoredExceptionHandler(oncrash); -#endif - if (_weaken(__sig_init)) - _weaken(__sig_init)(); +textwindows static int sys_fork_nt_parent(uint32_t dwCreationFlags) { - // jump back into function below - longjmp(jb, 1); + // allocate process object + struct Proc *proc; + if (!(proc = __proc_new())) + return -1; + + // get path of this executable + char16_t prog[PATH_MAX]; + unsigned got = GetModuleFileName(0, prog, ARRAYLEN(prog)); + if (!got || got >= ARRAYLEN(prog)) { + dll_make_first(&__proc.free, &proc->elem); + enomem(); + return -1; + } + + // spawn new process in suspended state + struct NtProcessInformation procinfo; + struct NtStartupInfo startinfo = { + .cb = sizeof(struct NtStartupInfo), + .dwFlags = kNtStartfUsestdhandles, + .hStdInput = g_fds.p[0].handle, + .hStdOutput = g_fds.p[1].handle, + .hStdError = g_fds.p[2].handle, + }; + if (!CreateProcess(prog, 0, 0, 0, true, + dwCreationFlags | kNtCreateSuspended | + kNtInheritParentAffinity | + kNtCreateUnicodeEnvironment | + GetPriorityClass(GetCurrentProcess()), + 0, 0, &startinfo, &procinfo)) { + STRACE("fork() %s() failed w/ %m %d", "CreateProcess", GetLastError()); + dll_make_first(&__proc.free, &proc->elem); + if (errno != ENOMEM) + eagain(); + return -1; + } + + // ensure process can be signaled before returning + UnmapViewOfFile(__sig_map_process(procinfo.dwProcessId, kNtOpenAlways)); + + // let's go + bool ok = true; + + // copy memory manager maps + for (struct MapSlab *slab = + atomic_load_explicit(&__maps.slabs, memory_order_acquire); + slab; slab = slab->next) { + ok = ok && !!VirtualAllocEx(procinfo.hProcess, slab, MAPS_SIZE, + kNtMemReserve | kNtMemCommit, kNtPageReadwrite); + ok = + ok && !!WriteProcessMemory(procinfo.hProcess, slab, slab, MAPS_SIZE, 0); + } + + // copy private memory maps + int alloc_prot = -1; + for (struct Map *map = __maps_first(); map; map = __maps_next(map)) { + if ((map->flags & MAP_TYPE) == MAP_SHARED) + continue; // shared memory doesn't need to be copied to subprocess + if ((map->flags & MAP_NOFORK) && (map->flags & MAP_TYPE) != MAP_FILE) + continue; // ignore things like signal worker stack memory + if (__maps_isalloc(map)) { + size_t allocsize = map->size; + for (struct Map *m2 = __maps_next(map); m2; m2 = __maps_next(m2)) { + if (!__maps_isalloc(m2) && map->addr + allocsize == m2->addr) { + allocsize += m2->size; + } else { + break; + } + } + if ((map->flags & MAP_NOFORK) && (map->flags & MAP_TYPE) == MAP_FILE) { + // portable executable segment + if (map->prot & PROT_EXEC) + // TODO(jart): write a __remorph_tls() function + continue; + if (!(map->prot & PROT_WRITE)) { + uint32_t child_old_protect; + ok = ok && !!VirtualProtectEx(procinfo.hProcess, map->addr, allocsize, + kNtPageReadwrite, &child_old_protect); + alloc_prot = PROT_READ | PROT_WRITE; + } else { + alloc_prot = map->prot; + } + } else { + // private mapping + uint32_t page_flags; + if (!(alloc_prot & PROT_WRITE)) { + page_flags = kNtPageReadwrite; + alloc_prot = PROT_READ | PROT_WRITE; + } else { + page_flags = __prot2nt(alloc_prot, false); + } + ok = ok && !!VirtualAllocEx(procinfo.hProcess, map->addr, allocsize, + kNtMemReserve | kNtMemCommit, page_flags); + } + } + uint32_t parent_old_protect; + if (!(map->prot & PROT_READ)) + ok = ok && !!VirtualProtect(map->addr, map->size, kNtPageReadwrite, + &parent_old_protect); + ok = ok && + !!WriteProcessMemory(procinfo.hProcess, map->addr, map->addr, + (map->size + __pagesize - 1) & -__pagesize, 0); + if (map->prot != alloc_prot) { + uint32_t child_old_protect; + ok = ok && + !!VirtualProtectEx(procinfo.hProcess, map->addr, map->size, + __prot2nt(map->prot, false), &child_old_protect); + } + if (!(map->prot & PROT_READ)) + ok = ok && !!VirtualProtect(map->addr, map->size, parent_old_protect, + &parent_old_protect); + } + + // set process loose + ok = ok && ResumeThread(procinfo.hThread) != -1u; + ok &= !!CloseHandle(procinfo.hThread); + + // return pid of new process + if (ok) { + proc->wasforked = true; + proc->handle = procinfo.hProcess; + proc->pid = procinfo.dwProcessId; + __proc_add(proc); + return procinfo.dwProcessId; + } else { + if (errno != ENOMEM) + eagain(); // posix fork() only specifies two errors + TerminateProcess(procinfo.hProcess, SIGKILL); + CloseHandle(procinfo.hProcess); + dll_make_first(&__proc.free, &proc->elem); + return -1; + } } textwindows int sys_fork_nt(uint32_t dwCreationFlags) { - char ok; - jmp_buf jb; - char **args; - int rc = -1; - struct Proc *proc; - struct CosmoTib *tib; - char16_t pipename[64]; - int64_t reader, writer; - struct NtStartupInfo startinfo; - struct NtProcessInformation procinfo; - char *p, forkvar[6 + 21 + 1 + 21 + 1]; - tib = __get_tls(); - if (!(proc = __proc_new())) - return -1; - ftrace_enabled(-1); - strace_enabled(-1); - if (!setjmp(jb)) { - reader = CreateNamedPipe(__create_pipe_name(pipename), kNtPipeAccessInbound, - kNtPipeTypeByte | kNtPipeReadmodeByte, 1, PIPE_BUF, - PIPE_BUF, 0, &kNtIsInheritable); - writer = CreateFile(pipename, kNtGenericWrite, 0, 0, kNtOpenExisting, 0, 0); - if (reader != -1 && writer != -1) { - p = stpcpy(forkvar, "_FORK="); - p = FormatUint64(p, reader); - bzero(&startinfo, sizeof(startinfo)); - startinfo.cb = sizeof(struct NtStartupInfo); - startinfo.dwFlags = kNtStartfUsestdhandles; - startinfo.hStdInput = g_fds.p[0].handle; - startinfo.hStdOutput = g_fds.p[1].handle; - startinfo.hStdError = g_fds.p[2].handle; - args = __argv; -#if SYSDEBUG - int i; - // If --strace was passed to this program, then propagate it the - // forked process since the flag was removed by __intercept_flag - if (strace_enabled(0) > 0) { - int n; - for (n = 0; args[n];) - ++n; -#pragma GCC push_options -#pragma GCC diagnostic ignored "-Walloca-larger-than=" - int nbytes = (n + 2) * sizeof(char *); - char **args2 = alloca(nbytes); - CheckLargeStackAllocation(args2, nbytes); -#pragma GCC pop_options - for (i = 0; i < n; ++i) - args2[i] = args[i]; - args2[i++] = "--strace"; - args2[i] = 0; - args = args2; - } -#endif - NTTRACE("STARTING SPAWN"); - int spawnrc = ntspawn(&(struct NtSpawnArgs){ - AT_FDCWD, GetProgramExecutableName(), args, environ, - (char *[]){forkvar, 0}, dwCreationFlags, 0, 0, 0, 0, &startinfo, - &procinfo}); - if (spawnrc != -1) { - CloseHandle(procinfo.hThread); - ok = WriteAll(writer, jb, sizeof(jb)); - // this list will be populated with the maps we're transferring - for (struct Map *map = __maps_first(); ok && map; - map = __maps_next(map)) { - if (map->flags & MAP_NOFORK) - continue; - if (MAX((char *)__executable_start, map->addr) < - MIN((char *)_end, map->addr + map->size)) - continue; // executable image is loaded by windows - ok = WriteAll(writer, map, sizeof(*map)); - } - // send a terminating Map struct to child - if (ok) { - struct Map map; - map.addr = MAP_FAILED; - ok = WriteAll(writer, &map, sizeof(map)); - } - // now write content of each map to child - int granularity = __gransize; - for (struct Map *map = __maps_first(); ok && map; - map = __maps_next(map)) { - if (map->flags & MAP_NOFORK) - continue; - // we only need to worry about the base mapping - if ((uintptr_t)map->addr & (granularity - 1)) - continue; - if (MAX((char *)__executable_start, map->addr) < - MIN((char *)_end, map->addr + map->size)) - continue; // executable image is loaded by windows - // shared mappings don't need to be copied - if ((map->flags & MAP_TYPE) == MAP_SHARED) - continue; - // get true length in case mprotect() chopped up actual win32 map - size_t size = map->size; - for (struct Map *map2 = __maps_next(map); map2; - map2 = __maps_next(map2)) { - if (map2->hand == -1 && map->addr + size == map2->addr) { - size += map2->size; - } else { - break; - } - } - for (struct Map *map2 = map; ok && map2; map2 = __maps_next(map2)) { - if (!(map2->prot & PROT_READ)) - if (map->addr >= map2->addr && map->addr < map->addr + size) - ok = VirtualProtect( - map2->addr, map2->size, - __prot2nt(map2->prot | PROT_READ, map2->iscow), - &map2->visited); - } - if (ok) - ok = WriteAll(writer, map->addr, size); - for (struct Map *map2 = map; ok && map2; map2 = __maps_next(map2)) { - if (!(map2->prot & PROT_READ)) - if (map->addr >= map2->addr && map->addr < map->addr + size) - ok = VirtualProtect(map2->addr, map2->size, map2->visited, - &map2->visited); - } - } - if (ok) - ok = WriteAll(writer, __data_start, __data_end - __data_start); - if (ok) - ok = WriteAll(writer, __bss_start, __bss_end - __bss_start); - if (ok) { - if (!CloseHandle(writer)) - ok = false; - writer = -1; - } - if (ok) { - proc->wasforked = true; - proc->handle = procinfo.hProcess; - rc = proc->pid = procinfo.dwProcessId; - __proc_add(proc); - } else { - TerminateProcess(procinfo.hProcess, SIGKILL); - CloseHandle(procinfo.hProcess); - rc = -1; - } - } - } - if (reader != -1) - CloseHandle(reader); - if (writer != -1) - CloseHandle(writer); - if (rc == -1 && errno != ENOMEM) - eagain(); // posix fork() only specifies two errors + int rc; + __winmain_isfork = true; + __winmain_tib = __get_tls(); + if (!__builtin_setjmp(__winmain_jmpbuf)) { + rc = sys_fork_nt_parent(dwCreationFlags); } else { + sys_fork_nt_child(); rc = 0; - // re-apply code morphing for thread-local storage - __tls_index = TlsAlloc(); - __set_tls_win32(tib); - __morph_tls(); - __tls_enabled = true; - // the child's pending signals is initially empty - atomic_store_explicit(&__sig.pending, 0, memory_order_relaxed); - atomic_store_explicit(&tib->tib_sigpending, 0, memory_order_relaxed); - // re-apply code morphing for function tracing - if (ftrace_stackdigs) { - _weaken(__hook)(_weaken(ftrace_hook), _weaken(GetSymbolTable)()); - } - // reset core runtime services - __proc_wipe(); - WipeKeystrokes(); - if (_weaken(__itimer_wipe)) - _weaken(__itimer_wipe)(); - // notify pthread join - atomic_store_explicit(&_pthread_static.ptid, GetCurrentThreadId(), - memory_order_release); } - if (rc == -1) - dll_make_first(&__proc.free, &proc->elem); - ftrace_enabled(+1); - strace_enabled(+1); + __winmain_isfork = false; return rc; } diff --git a/libc/proc/fork.c b/libc/proc/fork.c index bd0201517..fad92dc5a 100644 --- a/libc/proc/fork.c +++ b/libc/proc/fork.c @@ -16,124 +16,242 @@ │ 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/internal.h" +#include "libc/calls/sig.internal.h" #include "libc/calls/state.internal.h" -#include "libc/calls/struct/sigset.h" +#include "libc/calls/struct/metasigaltstack.h" #include "libc/calls/struct/sigset.internal.h" #include "libc/calls/struct/timespec.h" #include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" #include "libc/intrin/atomic.h" +#include "libc/intrin/cxaatexit.h" #include "libc/intrin/dll.h" -#include "libc/intrin/kprintf.h" #include "libc/intrin/maps.h" +#include "libc/intrin/stack.h" #include "libc/intrin/strace.h" #include "libc/intrin/weaken.h" #include "libc/nt/files.h" #include "libc/nt/process.h" #include "libc/nt/runtime.h" -#include "libc/nt/synchronization.h" #include "libc/nt/thread.h" -#include "libc/proc/proc.internal.h" +#include "libc/nt/thunk/msabi.h" +#include "libc/proc/proc.h" #include "libc/runtime/internal.h" -#include "libc/runtime/memtrack.internal.h" #include "libc/runtime/runtime.h" #include "libc/runtime/syslib.internal.h" -#include "libc/sysv/consts/sig.h" +#include "libc/stdio/internal.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/ss.h" +#include "libc/thread/itimer.h" #include "libc/thread/posixthread.internal.h" -#include "libc/thread/tls.h" +#include "libc/thread/thread.h" +#include "third_party/dlmalloc/dlmalloc.h" +#include "third_party/gdtoa/lock.h" +#include "third_party/tz/lock.h" -__static_yoink("_pthread_atfork"); +__msabi extern typeof(GetCurrentProcessId) *const __imp_GetCurrentProcessId; -extern pthread_mutex_t _rand64_lock_obj; -extern pthread_mutex_t _pthread_lock_obj; +extern atomic_int __sig_worker_state; +extern pthread_mutex_t __cxa_lock_obj; +extern pthread_mutex_t __pthread_lock_obj; -// fork needs to lock every lock, which makes it very single-threaded in -// nature. the outermost lock matters the most because it serializes all -// threads attempting to spawn processes. the outer lock can't be a spin -// lock that a pthread_atfork() caller slipped in. to ensure it's a fair -// lock, we add an additional one of our own, which protects other locks -static pthread_mutex_t _fork_gil = PTHREAD_MUTEX_INITIALIZER; +void __rand64_lock(void); +void __rand64_unlock(void); +void __rand64_wipe(void); -static void _onfork_prepare(void) { +void __dlopen_lock(void); +void __dlopen_unlock(void); +void __dlopen_wipe(void); + +// first and last and always +// it is the lord of all locks +// subordinate to no other lock +static pthread_mutex_t supreme_lock = PTHREAD_MUTEX_INITIALIZER; + +static void fork_prepare_stdio(void) { + struct Dll *e; + // we acquire the following locks, in order + // + // 1. FILE objects created by the user + // 2. stdin, stdout, and stderr + // 3. __stdio.lock + // +StartOver: + __stdio_lock(); + for (e = dll_last(__stdio.files); e; e = dll_prev(__stdio.files, e)) { + FILE *f = FILE_CONTAINER(e); + if (f->forking) + continue; + f->forking = 1; + __stdio_ref(f); + __stdio_unlock(); + _pthread_mutex_lock(&f->lock); + __stdio_unref(f); + goto StartOver; + } +} + +static void fork_parent_stdio(void) { + struct Dll *e; + for (e = dll_first(__stdio.files); e; e = dll_next(__stdio.files, e)) { + FILE_CONTAINER(e)->forking = 0; + _pthread_mutex_unlock(&FILE_CONTAINER(e)->lock); + } + __stdio_unlock(); +} + +static void fork_child_stdio(void) { + struct Dll *e; + for (e = dll_first(__stdio.files); e; e = dll_next(__stdio.files, e)) { + _pthread_mutex_wipe_np(&FILE_CONTAINER(e)->lock); + FILE_CONTAINER(e)->forking = 0; + } + _pthread_mutex_wipe_np(&__stdio.lock); +} + +static void fork_prepare(void) { + _pthread_mutex_lock(&supreme_lock); if (_weaken(_pthread_onfork_prepare)) _weaken(_pthread_onfork_prepare)(); + fork_prepare_stdio(); + if (_weaken(__localtime_lock)) + _weaken(__localtime_lock)(); + if (_weaken(__dlopen_lock)) + _weaken(__dlopen_lock)(); if (IsWindows()) __proc_lock(); + if (_weaken(cosmo_stack_lock)) + _weaken(cosmo_stack_lock)(); + __cxa_lock(); + if (_weaken(__gdtoa_lock)) { + _weaken(__gdtoa_lock1)(); + _weaken(__gdtoa_lock)(); + } _pthread_lock(); - __maps_lock(); + if (_weaken(dlmalloc_pre_fork)) + _weaken(dlmalloc_pre_fork)(); __fds_lock(); - pthread_mutex_lock(&_rand64_lock_obj); - LOCKTRACE("READY TO ROCK AND ROLL"); + if (_weaken(__rand64_lock)) + _weaken(__rand64_lock)(); + __maps_lock(); + LOCKTRACE("READY TO LOCK AND ROLL"); } -static void _onfork_parent(void) { - pthread_mutex_unlock(&_rand64_lock_obj); - __fds_unlock(); +static void fork_parent(void) { __maps_unlock(); + if (_weaken(__rand64_unlock)) + _weaken(__rand64_unlock)(); + __fds_unlock(); + if (_weaken(dlmalloc_post_fork_parent)) + _weaken(dlmalloc_post_fork_parent)(); _pthread_unlock(); + if (_weaken(__gdtoa_unlock)) { + _weaken(__gdtoa_unlock)(); + _weaken(__gdtoa_unlock1)(); + } + __cxa_unlock(); + if (_weaken(cosmo_stack_unlock)) + _weaken(cosmo_stack_unlock)(); if (IsWindows()) __proc_unlock(); + if (_weaken(__dlopen_unlock)) + _weaken(__dlopen_unlock)(); + if (_weaken(__localtime_unlock)) + _weaken(__localtime_unlock)(); + fork_parent_stdio(); if (_weaken(_pthread_onfork_parent)) _weaken(_pthread_onfork_parent)(); + _pthread_mutex_unlock(&supreme_lock); } -static void _onfork_child(void) { - if (IsWindows()) - __proc_wipe(); - __fds_lock_obj = (pthread_mutex_t)PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; - _rand64_lock_obj = (pthread_mutex_t)PTHREAD_SIGNAL_SAFE_MUTEX_INITIALIZER_NP; - _pthread_lock_obj = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; - atomic_store_explicit(&__maps.lock, 0, memory_order_relaxed); +static void fork_child(int ppid_win32, int ppid_cosmo) { + if (_weaken(__rand64_wipe)) + _weaken(__rand64_wipe)(); + _pthread_mutex_wipe_np(&__fds_lock_obj); + dlmalloc_post_fork_child(); + if (_weaken(__gdtoa_wipe)) { + _weaken(__gdtoa_wipe)(); + _weaken(__gdtoa_wipe1)(); + } + fork_child_stdio(); + _pthread_mutex_wipe_np(&__pthread_lock_obj); + _pthread_mutex_wipe_np(&__cxa_lock_obj); + if (_weaken(cosmo_stack_wipe)) + _weaken(cosmo_stack_wipe)(); + if (_weaken(__dlopen_wipe)) + _weaken(__dlopen_wipe)(); + if (_weaken(__localtime_wipe)) + _weaken(__localtime_wipe)(); + if (IsWindows()) { + // we don't bother locking the proc/itimer/sig locks above since + // their state is reset in the forked child. nothing to protect. + sys_read_nt_wipe_keystrokes(); + __proc_wipe_and_reset(); + __itimer_wipe_and_reset(); + atomic_init(&__sig_worker_state, 0); + if (_weaken(__sig_init)) + _weaken(__sig_init)(); + if (_weaken(sys_getppid_nt_wipe)) + _weaken(sys_getppid_nt_wipe)(ppid_win32, ppid_cosmo); + } if (_weaken(_pthread_onfork_child)) _weaken(_pthread_onfork_child)(); + _pthread_mutex_wipe_np(&supreme_lock); } -static int _forker(uint32_t dwCreationFlags) { - long micros; +int _fork(uint32_t dwCreationFlags) { struct Dll *e; - struct timespec started; - int ax, dx, tid, parent; - parent = __pid; - started = timespec_mono(); - _onfork_prepare(); + int ax, dx, tid, ppid_win32, ppid_cosmo; + ppid_win32 = IsWindows() ? GetCurrentProcessId() : 0; + ppid_cosmo = __pid; + BLOCK_SIGNALS; + fork_prepare(); if (!IsWindows()) { ax = sys_fork(); } else { ax = sys_fork_nt(dwCreationFlags); } - micros = timespec_tomicros(timespec_sub(timespec_mono(), started)); if (!ax) { // get new process id if (!IsWindows()) { dx = sys_getpid().ax; } else { - dx = GetCurrentProcessId(); + dx = __imp_GetCurrentProcessId(); } __pid = dx; + // get new thread id + struct CosmoTib *tib = __get_tls(); + struct PosixThread *me = (struct PosixThread *)tib->tib_pthread; + tid = IsLinux() || IsXnuSilicon() ? dx : sys_gettid(); + atomic_init(&tib->tib_ctid, tid); + atomic_init(&tib->tib_ptid, tid); + + // tracing and kisdangerous need this lock wiped a little earlier + atomic_init(&__maps.lock.word, 0); + + /* + * it's now safe to call normal functions again + */ + + // this wipe must happen fast + void nsync_waiter_wipe_(void); + if (_weaken(nsync_waiter_wipe_)) + _weaken(nsync_waiter_wipe_)(); + // turn other threads into zombies // we can't free() them since we're monopolizing all locks // we assume the operating system already reclaimed system handles - struct CosmoTib *tib = __get_tls(); - struct PosixThread *pt = (struct PosixThread *)tib->tib_pthread; - dll_remove(&_pthread_list, &pt->list); - for (e = dll_first(_pthread_list); e; e = dll_next(_pthread_list, e)) { - atomic_store_explicit(&POSIXTHREAD_CONTAINER(e)->pt_status, - kPosixThreadZombie, memory_order_relaxed); - atomic_store_explicit(&POSIXTHREAD_CONTAINER(e)->tib->tib_syshand, 0, - memory_order_relaxed); - } - dll_make_first(&_pthread_list, &pt->list); - - // get new main thread id - tid = IsLinux() || IsXnuSilicon() ? dx : sys_gettid(); - atomic_store_explicit(&tib->tib_tid, tid, memory_order_relaxed); - atomic_store_explicit(&pt->ptid, tid, memory_order_relaxed); + dll_remove(&_pthread_list, &me->list); + struct Dll *old_threads = _pthread_list; + _pthread_list = 0; + dll_make_first(&_pthread_list, &me->list); + atomic_init(&_pthread_count, 1); // get new system thread handle intptr_t syshand = 0; @@ -144,34 +262,56 @@ static int _forker(uint32_t dwCreationFlags) { GetCurrentProcess(), &syshand, 0, false, kNtDuplicateSameAccess); } - atomic_store_explicit(&tib->tib_syshand, syshand, memory_order_relaxed); + atomic_init(&tib->tib_syshand, syshand); + + // the child's pending signals is initially empty + atomic_init(&tib->tib_sigpending, 0); // we can't be canceled if the canceler no longer exists - atomic_store_explicit(&pt->pt_canceled, false, memory_order_relaxed); + atomic_init(&me->pt_canceled, false); + + // forget locks + bzero(tib->tib_locks, sizeof(tib->tib_locks)); + + // xnu fork() doesn't preserve sigaltstack() + if (IsXnu() && me->tib->tib_sigstack_addr) { + struct sigaltstack_bsd ss; + ss.ss_sp = me->tib->tib_sigstack_addr; + ss.ss_size = me->tib->tib_sigstack_size; + ss.ss_flags = me->tib->tib_sigstack_flags; + if (IsXnuSilicon()) { + __syslib->__sigaltstack(&ss, 0); + } else { + sys_sigaltstack(&ss, 0); + } + } // run user fork callbacks - _onfork_child(); - STRACE("fork() → 0 (child of %d; took %ld us)", parent, micros); + fork_child(ppid_win32, ppid_cosmo); + + // free threads + if (_weaken(_pthread_free)) { + while ((e = dll_first(old_threads))) { + struct PosixThread *pt = POSIXTHREAD_CONTAINER(e); + atomic_init(&pt->tib->tib_syshand, 0); + dll_remove(&old_threads, e); + _weaken(_pthread_free)(pt); + } + } + + // reactivate ftrace + /* if (ftrace_stackdigs) */ + /* if (_weaken(ftrace_install)) */ + /* _weaken(ftrace_install)(); */ + + STRACE("fork() → 0 (child of %d)", ppid_cosmo); } else { // this is the parent process - _onfork_parent(); - STRACE("fork() → %d% m (took %ld us)", ax, micros); - } - return ax; -} - -int _fork(uint32_t dwCreationFlags) { - int rc; - BLOCK_SIGNALS; - pthread_mutex_lock(&_fork_gil); - rc = _forker(dwCreationFlags); - if (!rc) { - pthread_mutex_init(&_fork_gil, 0); - } else { - pthread_mutex_unlock(&_fork_gil); + fork_parent(); + STRACE("fork() → %d% m", ax); } ALLOW_SIGNALS; - return rc; + return ax; } /** diff --git a/libc/intrin/pthread_atfork_actual.c b/libc/proc/getppid-nt.c similarity index 50% rename from libc/intrin/pthread_atfork_actual.c rename to libc/proc/getppid-nt.c index 505cbdc96..c602042e6 100644 --- a/libc/intrin/pthread_atfork_actual.c +++ b/libc/proc/getppid-nt.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2022 Justine Alexandra Roberts Tunney │ +│ Copyright 2021 Justine Alexandra Roberts Tunney │ │ │ │ Permission to use, copy, modify, and/or distribute this software for │ │ any purpose with or without fee is hereby granted, provided that the │ @@ -17,85 +17,77 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/atomic.h" -#include "libc/calls/state.internal.h" +#include "libc/calls/syscall-nt.internal.h" #include "libc/cosmo.h" #include "libc/dce.h" -#include "libc/errno.h" -#include "libc/intrin/atomic.h" -#include "libc/intrin/dll.h" -#include "libc/intrin/strace.h" -#include "libc/macros.h" -#include "libc/proc/proc.internal.h" +#include "libc/fmt/itoa.h" +#include "libc/nt/enum/status.h" +#include "libc/nt/nt/process.h" +#include "libc/nt/process.h" +#include "libc/nt/runtime.h" +#include "libc/nt/struct/processbasicinformation.h" +#include "libc/runtime/internal.h" #include "libc/runtime/runtime.h" -#include "libc/str/str.h" -#include "libc/thread/posixthread.internal.h" -#include "libc/thread/thread.h" -#include "libc/thread/tls.h" -struct AtFork { - struct AtFork *p[2]; - atfork_f f[3]; -}; +int sys_getppid_nt_win32; +int sys_getppid_nt_cosmo; -static struct AtForks { - pthread_spinlock_t lock; - struct AtFork *list; - struct AtFork pool[64]; - atomic_int allocated; -} _atforks; +textwindows static int sys_getppid_nt_ntdll(void) { + struct NtProcessBasicInformation ProcessInformation; + uint32_t gotsize = 0; + if (!NtError( + NtQueryInformationProcess(GetCurrentProcess(), 0, &ProcessInformation, + sizeof(ProcessInformation), &gotsize)) && + gotsize >= sizeof(ProcessInformation) && + ProcessInformation.InheritedFromUniqueProcessId) { + return ProcessInformation.InheritedFromUniqueProcessId; + } + return 0; +} -static void _pthread_onfork(int i, const char *op) { - struct AtFork *a; - if (!i) - pthread_spin_lock(&_atforks.lock); - for (a = _atforks.list; a; a = a->p[!i]) { - if (a->f[i]) { - STRACE("pthread_atfork(%s, %t)", op, a->f[i]); - a->f[i](); +static void sys_getppid_nt_extract(const char *str) { + int c; + int win32 = 0; + int cosmo = 0; + if (str) { + for (;;) { + c = *str; + if (!('0' <= c && c <= '9')) + break; + win32 *= 10; + win32 += c - '0'; + ++str; + } + if (win32 && *str++ == ':') { + for (;;) { + c = *str; + if (!('0' <= c && c <= '9')) + break; + cosmo *= 10; + cosmo += c - '0'; + ++str; + } + if (win32 == sys_getppid_nt_ntdll()) { + sys_getppid_nt_win32 = win32; + sys_getppid_nt_cosmo = cosmo; + } } - _atforks.list = a; - } - if (i) - pthread_spin_unlock(&_atforks.lock); -} - -void _pthread_onfork_prepare(void) { - _pthread_onfork(0, "prepare"); -} - -void _pthread_onfork_parent(void) { - _pthread_onfork(1, "parent"); -} - -void _pthread_onfork_child(void) { - _pthread_onfork(2, "child"); -} - -static struct AtFork *_pthread_atfork_alloc(void) { - int i, n = ARRAYLEN(_atforks.pool); - if (atomic_load_explicit(&_atforks.allocated, memory_order_relaxed) < n && - (i = atomic_fetch_add(&_atforks.allocated, 1)) < n) { - return _atforks.pool + i; - } else { - return 0; } } -int _pthread_atfork(atfork_f prepare, atfork_f parent, atfork_f child) { - int rc; - struct AtFork *a; - if (!(a = _pthread_atfork_alloc())) - return ENOMEM; - a->f[0] = prepare; - a->f[1] = parent; - a->f[2] = child; - pthread_spin_lock(&_atforks.lock); - a->p[0] = 0; - a->p[1] = _atforks.list; - if (_atforks.list) - _atforks.list->p[0] = a; - _atforks.list = a; - pthread_spin_unlock(&_atforks.lock); - rc = 0; - return rc; +__attribute__((__constructor__(90))) static void init(void) { + if (!IsWindows()) + return; + sys_getppid_nt_extract(getenv("_COSMO_PPID")); +} + +textwindows int sys_getppid_nt(void) { + if (sys_getppid_nt_cosmo) + return sys_getppid_nt_cosmo; + return sys_getppid_nt_ntdll(); +} + +textwindows void sys_getppid_nt_wipe(int win32, int cosmo) { + sys_getppid_nt_win32 = win32; + sys_getppid_nt_cosmo = cosmo; } diff --git a/libc/calls/getppid.c b/libc/proc/getppid.c similarity index 100% rename from libc/calls/getppid.c rename to libc/proc/getppid.c diff --git a/libc/proc/getpriority-nt.c b/libc/proc/getpriority-nt.c index 67d84363c..ff4fca305 100644 --- a/libc/proc/getpriority-nt.c +++ b/libc/proc/getpriority-nt.c @@ -22,7 +22,7 @@ #include "libc/nt/errors.h" #include "libc/nt/process.h" #include "libc/nt/runtime.h" -#include "libc/proc/proc.internal.h" +#include "libc/proc/proc.h" #include "libc/sysv/consts/prio.h" #include "libc/sysv/errfuns.h" diff --git a/libc/proc/getrusage-nt.c b/libc/proc/getrusage-nt.c index 2b0917843..07bde103a 100644 --- a/libc/proc/getrusage-nt.c +++ b/libc/proc/getrusage-nt.c @@ -29,7 +29,7 @@ #include "libc/nt/struct/iocounters.h" #include "libc/nt/struct/processmemorycounters.h" #include "libc/nt/thread.h" -#include "libc/proc/proc.internal.h" +#include "libc/proc/proc.h" #include "libc/str/str.h" #include "libc/sysv/consts/rusage.h" #include "libc/sysv/errfuns.h" @@ -58,9 +58,8 @@ textwindows int sys_getrusage_nt(int who, struct rusage *usage) { return einval(); } - if (!usage) { + if (!usage) return 0; - } if (!(who == RUSAGE_THREAD ? GetThreadTimes : GetProcessTimes)( me, &ftCreation, &ftExit, &ftKernel, &ftUser) || diff --git a/libc/proc/handle.c b/libc/proc/handle.c index 10c220328..83c134e4a 100644 --- a/libc/proc/handle.c +++ b/libc/proc/handle.c @@ -19,7 +19,7 @@ #include "libc/calls/calls.h" #include "libc/intrin/weaken.h" #include "libc/nt/runtime.h" -#include "libc/proc/proc.internal.h" +#include "libc/proc/proc.h" // retrieves handle of process // supports only current process and processes we created diff --git a/libc/proc/kill-nt.c b/libc/proc/kill-nt.c index 45f9b740d..a820bf932 100644 --- a/libc/proc/kill-nt.c +++ b/libc/proc/kill-nt.c @@ -16,19 +16,24 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/atomic.h" #include "libc/calls/calls.h" +#include "libc/calls/sig.internal.h" #include "libc/calls/struct/sigset.internal.h" #include "libc/calls/syscall-nt.internal.h" #include "libc/errno.h" +#include "libc/intrin/atomic.h" #include "libc/intrin/dll.h" #include "libc/intrin/strace.h" #include "libc/nt/console.h" +#include "libc/nt/enum/creationdisposition.h" #include "libc/nt/enum/ctrlevent.h" #include "libc/nt/enum/processaccess.h" #include "libc/nt/errors.h" +#include "libc/nt/memory.h" #include "libc/nt/process.h" #include "libc/nt/runtime.h" -#include "libc/proc/proc.internal.h" +#include "libc/proc/proc.h" #include "libc/sysv/consts/sig.h" #include "libc/sysv/errfuns.h" #ifdef __x86_64__ @@ -36,31 +41,39 @@ textwindows int sys_kill_nt(int pid, int sig) { // validate api usage - if (!(0 <= sig && sig <= 64)) { + if (!(0 <= sig && sig <= 64)) return einval(); - } // XXX: NT doesn't really have process groups. For instance the // CreateProcess() flag for starting a process group actually // just does an "ignore ctrl-c" internally. - if (pid < -1) { + if (pid < -1) pid = -pid; - } // no support for kill all yet - if (pid == -1) { + if (pid == -1) return einval(); - } // just call raise() if we're targeting self if (pid <= 0 || pid == getpid()) { if (sig) { if (pid <= 0) { + // if pid is 0 or -1 then kill the processes beneath us too. + // this isn't entirely right but it's closer to being right. + // having this behavior is helpful for servers like redbean. struct Dll *e; BLOCK_SIGNALS; __proc_lock(); - for (e = dll_first(__proc.list); e; e = dll_next(__proc.list, e)) - TerminateProcess(PROC_CONTAINER(e)->handle, sig); + for (e = dll_first(__proc.list); e; e = dll_next(__proc.list, e)) { + atomic_ulong *sigproc; + struct Proc *pr = PROC_CONTAINER(e); + if (sig != 9 && (sigproc = __sig_map_process(pid, kNtOpenExisting))) { + atomic_fetch_or_explicit(sigproc, 1ull << (sig - 1), + memory_order_release); + } else { + TerminateProcess(pr->handle, sig); + } + } __proc_unlock(); ALLOW_SIGNALS; } @@ -70,33 +83,45 @@ textwindows int sys_kill_nt(int pid, int sig) { } } + // attempt to signal via shared memory file + // + // now that we know the process exists, if it has a shared memory file + // then we can be reasonably certain it's a cosmo process which should + // be trusted to deliver its signal, unless it's a nine exterminations + if (pid > 0 && sig != 9) { + atomic_ulong *sigproc; + if ((sigproc = __sig_map_process(pid, kNtOpenExisting))) { + if (sig > 0) + atomic_fetch_or_explicit(sigproc, 1ull << (sig - 1), + memory_order_release); + UnmapViewOfFile(sigproc); + if (sig != 9) + return 0; + } + } + // find existing handle we own for process + // + // this step should come first to verify process existence. this is + // because there's no guarantee that just because the shared memory + // file exists, the process actually exists. int64_t handle, closeme = 0; if (!(handle = __proc_handle(pid))) { - if ((handle = OpenProcess(kNtProcessTerminate, false, pid))) { - closeme = handle; - } else { - goto OnError; - } + if (!(handle = OpenProcess(kNtProcessTerminate, false, pid))) + return eperm(); + closeme = handle; } // perform actual kill // process will report WIFSIGNALED with WTERMSIG(sig) + if (sig != 9) + STRACE("warning: kill() sending %G via terminate", sig); bool32 ok = TerminateProcess(handle, sig); if (closeme) CloseHandle(closeme); if (ok) return 0; - - // handle error -OnError: - switch (GetLastError()) { - case kNtErrorInvalidHandle: - case kNtErrorInvalidParameter: - return esrch(); - default: - return eperm(); - } + return esrch(); } #endif /* __x86_64__ */ diff --git a/libc/proc/kill.c b/libc/proc/kill.c index 9c0e99a6e..5de445fd3 100644 --- a/libc/proc/kill.c +++ b/libc/proc/kill.c @@ -29,6 +29,20 @@ * The impact of this action can be terminating the process, or * interrupting it to request something happen. * + * On Windows, signals are delivered between processes using shared + * memory files stored in C:\ProgramData\cosmo\sig\x\y.pid which hold + * the process signal mask. Any process that can access these files can + * signal a cosmo process. The targeting process will then notice that a + * signal has been added and delivers to any thread as soon as possible. + * + * On Windows, the only signal that's guaranteed to work on non-cosmocc + * processes is SIGKILL. + * + * On Windows, the concept of a process group isn't fully implemented. + * Saying `kill(0, sig)` will deliver `sig` to all direct descendent + * processes. Saying `kill(-pid, sig)` will be the same as saying + * `kill(pid, sig)`. + * * @param pid can be: * >0 signals one process by id * =0 signals all processes in current process group diff --git a/libc/proc/posix_spawn.c b/libc/proc/posix_spawn.c index 434702d77..4dbbdcea9 100644 --- a/libc/proc/posix_spawn.c +++ b/libc/proc/posix_spawn.c @@ -51,6 +51,7 @@ #include "libc/nt/enum/processcreationflags.h" #include "libc/nt/enum/startf.h" #include "libc/nt/files.h" +#include "libc/nt/process.h" #include "libc/nt/runtime.h" #include "libc/nt/struct/processinformation.h" #include "libc/nt/struct/startupinfo.h" @@ -58,7 +59,8 @@ #include "libc/proc/ntspawn.h" #include "libc/proc/posix_spawn.h" #include "libc/proc/posix_spawn.internal.h" -#include "libc/proc/proc.internal.h" +#include "libc/proc/proc.h" +#include "libc/runtime/internal.h" #include "libc/runtime/runtime.h" #include "libc/sock/sock.h" #include "libc/stdio/stdio.h" @@ -350,12 +352,8 @@ static textwindows errno_t posix_spawn_nt_impl( // figure out flags uint32_t dwCreationFlags = 0; short flags = attrp && *attrp ? (*attrp)->flags : 0; - if (flags & POSIX_SPAWN_SETSID) { - dwCreationFlags |= kNtDetachedProcess; - } - if (flags & POSIX_SPAWN_SETPGROUP) { + if (flags & (POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSID)) dwCreationFlags |= kNtCreateNewProcessGroup; - } // create process startinfo struct NtStartupInfo startinfo = { @@ -400,6 +398,14 @@ static textwindows errno_t posix_spawn_nt_impl( } FormatUint64(stpcpy(maskvar, "_MASK="), childmask); + // inherit parent process id + char ppidvar[12 + 21 + 1 + 21 + 1], *p = ppidvar; + p = stpcpy(p, "_COSMO_PPID="); + p = FormatUint64(p, GetCurrentProcessId()); + *p++ = ':'; + p = FormatUint64(p, __pid); + setenv("_COSMO_PPID", ppidvar, true); + // launch process int rc = -1; struct NtProcessInformation procinfo; @@ -616,7 +622,7 @@ errno_t posix_spawn(int *pid, const char *path, struct sigaction dfl = {0}; if (use_pipe) close(pfds[0]); - for (int sig = 1; sig < _NSIG; sig++) + for (int sig = 1; sig <= NSIG; sig++) if (__sighandrvas[sig] != (long)SIG_DFL && (__sighandrvas[sig] != (long)SIG_IGN || ((flags & POSIX_SPAWN_SETSIGDEF) && diff --git a/libc/proc/proc.c b/libc/proc/proc.c index bdada4c46..0288075d1 100644 --- a/libc/proc/proc.c +++ b/libc/proc/proc.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/proc/proc.h" #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/calls/sig.internal.h" @@ -23,6 +24,7 @@ #include "libc/calls/struct/rusage.h" #include "libc/calls/struct/siginfo.h" #include "libc/calls/struct/sigset.internal.h" +#include "libc/calls/syscall_support-nt.internal.h" #include "libc/cosmo.h" #include "libc/errno.h" #include "libc/fmt/wintime.internal.h" @@ -32,11 +34,13 @@ #include "libc/intrin/weaken.h" #include "libc/mem/leaks.h" #include "libc/nt/accounting.h" +#include "libc/nt/enum/heap.h" #include "libc/nt/enum/processaccess.h" #include "libc/nt/enum/processcreationflags.h" #include "libc/nt/enum/status.h" #include "libc/nt/enum/wait.h" #include "libc/nt/events.h" +#include "libc/nt/memory.h" #include "libc/nt/process.h" #include "libc/nt/runtime.h" #include "libc/nt/struct/filetime.h" @@ -44,7 +48,6 @@ #include "libc/nt/struct/processmemorycounters.h" #include "libc/nt/synchronization.h" #include "libc/nt/thread.h" -#include "libc/proc/proc.internal.h" #include "libc/runtime/runtime.h" #include "libc/str/str.h" #include "libc/sysv/consts/map.h" @@ -53,6 +56,7 @@ #include "libc/sysv/consts/sicode.h" #include "libc/sysv/consts/sig.h" #include "libc/sysv/errfuns.h" +#include "libc/thread/thread.h" #include "libc/thread/tls.h" #include "third_party/nsync/mu.h" #ifdef __x86_64__ @@ -64,8 +68,9 @@ #define STACK_SIZE 65536 struct Procs __proc; +static pthread_mutex_t __proc_lock_obj = PTHREAD_MUTEX_INITIALIZER; -static textwindows void __proc_stats(int64_t h, struct rusage *ru) { +textwindows static void __proc_stats(int64_t h, struct rusage *ru) { bzero(ru, sizeof(*ru)); struct NtProcessMemoryCountersEx memcount = {sizeof(memcount)}; GetProcessMemoryInfo(h, &memcount, sizeof(memcount)); @@ -101,14 +106,14 @@ textwindows int __proc_harvest(struct Proc *pr, bool iswait4) { pr->handle = status & 0x00FFFFFF; } else { // handle child _Exit() - if (status == 0xc9af3d51u) { + if (status == 0xc9af3d51u) status = kNtStillActive; - } pr->wstatus = status; if (!iswait4 && !pr->waiters && !__proc.waiters && (__sighandrvas[SIGCHLD] == (uintptr_t)SIG_IGN || (__sighandflags[SIGCHLD] & SA_NOCLDWAIT))) { // perform automatic zombie reaping + STRACE("automatically reaping zombie"); dll_remove(&__proc.list, &pr->elem); dll_make_first(&__proc.free, &pr->elem); CloseHandle(pr->handle); @@ -131,13 +136,14 @@ textwindows int __proc_harvest(struct Proc *pr, bool iswait4) { return sic; } -static textwindows dontinstrument uint32_t __proc_worker(void *arg) { +textwindows dontinstrument static uint32_t __proc_worker(void *arg) { struct CosmoTib tls; char *sp = __builtin_frame_address(0); __bootstrap_tls(&tls, __builtin_frame_address(0)); __maps_track( (char *)(((uintptr_t)sp + __pagesize - 1) & -__pagesize) - STACK_SIZE, - STACK_SIZE); + STACK_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_NOFORK); for (;;) { // assemble a group of processes to wait on. if more than 64 @@ -169,7 +175,7 @@ static textwindows dontinstrument uint32_t __proc_worker(void *arg) { // wait for something to happen if (n == 64) { - millis = 5; + millis = POLL_INTERVAL_MS; } else { millis = -1u; handles[n++] = __proc.onbirth; @@ -191,9 +197,8 @@ static textwindows dontinstrument uint32_t __proc_worker(void *arg) { continue; if (j == i) continue; - if (!--objects[j]->waiters && objects[j]->status == PROC_UNDEAD) { + if (!--objects[j]->waiters && objects[j]->status == PROC_UNDEAD) __proc_free(objects[j]); - } } // check if we need to churn due to >64 processes @@ -218,9 +223,8 @@ static textwindows dontinstrument uint32_t __proc_worker(void *arg) { case PROC_ZOMBIE: break; case PROC_UNDEAD: - if (!objects[i]->waiters) { + if (!objects[i]->waiters) __proc_free(objects[i]); - } break; default: __builtin_unreachable(); @@ -232,9 +236,8 @@ static textwindows dontinstrument uint32_t __proc_worker(void *arg) { // 1. wait4() is being used // 2. SIGCHLD has SIG_IGN handler // 3. SIGCHLD has SA_NOCLDWAIT flag - if (sic) { + if (sic) __sig_generate(SIGCHLD, sic); - } } return 0; } @@ -242,7 +245,7 @@ static textwindows dontinstrument uint32_t __proc_worker(void *arg) { /** * Lazy initializes process tracker data structures and worker. */ -static textwindows void __proc_setup(void) { +textwindows static void __proc_setup(void) { __proc.onbirth = CreateEvent(0, 0, 0, 0); // auto reset __proc.haszombies = CreateEvent(0, 1, 0, 0); // manual reset __proc.thread = CreateThread(0, STACK_SIZE, __proc_worker, 0, @@ -254,20 +257,22 @@ static textwindows void __proc_setup(void) { */ textwindows void __proc_lock(void) { cosmo_once(&__proc.once, __proc_setup); - nsync_mu_lock(&__proc.lock); + _pthread_mutex_lock(&__proc_lock_obj); } /** * Unlocks process tracker. */ textwindows void __proc_unlock(void) { - nsync_mu_unlock(&__proc.lock); + _pthread_mutex_unlock(&__proc_lock_obj); } /** * Resets process tracker from forked child. */ -textwindows void __proc_wipe(void) { +textwindows void __proc_wipe_and_reset(void) { + // TODO(jart): Should we preserve this state in forked children? + _pthread_mutex_wipe_np(&__proc_lock_obj); bzero(&__proc, sizeof(__proc)); } @@ -286,16 +291,9 @@ textwindows struct Proc *__proc_new(void) { proc = PROC_CONTAINER(e); dll_remove(&__proc.free, &proc->elem); } - if (proc) { - bzero(proc, sizeof(*proc)); - } else { - proc = mmap(0, sizeof(struct Proc), PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (proc == MAP_FAILED) { - enomem(); - return 0; - } - } + if (!proc && !(proc = HeapAlloc(GetProcessHeap(), 0, sizeof(struct Proc)))) + return 0; + bzero(proc, sizeof(*proc)); dll_init(&proc->elem); return proc; } @@ -323,6 +321,7 @@ textwindows int64_t __proc_search(int pid) { int64_t handle = 0; BLOCK_SIGNALS; __proc_lock(); + // TODO(jart): we should increment a reference count when returning for (e = dll_first(__proc.list); e; e = dll_next(__proc.list, e)) { if (pid == PROC_CONTAINER(e)->pid) { handle = PROC_CONTAINER(e)->handle; diff --git a/libc/proc/proc.internal.h b/libc/proc/proc.h similarity index 64% rename from libc/proc/proc.internal.h rename to libc/proc/proc.h index fd59bc5f1..44b4ed5ad 100644 --- a/libc/proc/proc.internal.h +++ b/libc/proc/proc.h @@ -5,7 +5,6 @@ #include "libc/intrin/atomic.h" #include "libc/intrin/dll.h" #include "libc/thread/thread.h" -#include "third_party/nsync/mu.h" COSMOPOLITAN_C_START_ #define PROC_ALIVE 0 @@ -28,7 +27,6 @@ struct Proc { struct Procs { int waiters; atomic_uint once; - nsync_mu lock; intptr_t thread; intptr_t onbirth; intptr_t haszombies; @@ -42,16 +40,16 @@ struct Procs { extern struct Procs __proc; -void __proc_wipe(void) libcesque; -void __proc_lock(void) libcesque; -void __proc_unlock(void) libcesque; -int64_t __proc_handle(int) libcesque; -int64_t __proc_search(int) libcesque; -struct Proc *__proc_new(void) libcesque; -void __proc_add(struct Proc *) libcesque; -void __proc_free(struct Proc *) libcesque; -int __proc_harvest(struct Proc *, bool) libcesque; -int sys_wait4_nt(int, int *, int, struct rusage *) libcesque; +void __proc_lock(void); +void __proc_unlock(void); +int64_t __proc_handle(int); +int64_t __proc_search(int); +struct Proc *__proc_new(void); +void __proc_add(struct Proc *); +void __proc_free(struct Proc *); +void __proc_wipe_and_reset(void); +int __proc_harvest(struct Proc *, bool); +int sys_wait4_nt(int, int *, int, struct rusage *); COSMOPOLITAN_C_END_ #endif /* COSMOPOLITAN_LIBC_PROC_H_ */ diff --git a/libc/proc/sched_getaffinity.c b/libc/proc/sched_getaffinity.c index 5bddf33bf..752bc9c79 100644 --- a/libc/proc/sched_getaffinity.c +++ b/libc/proc/sched_getaffinity.c @@ -24,7 +24,7 @@ #include "libc/nt/errors.h" #include "libc/nt/process.h" #include "libc/nt/runtime.h" -#include "libc/proc/proc.internal.h" +#include "libc/proc/proc.h" #include "libc/str/str.h" #include "libc/sysv/errfuns.h" diff --git a/libc/proc/sched_setaffinity.c b/libc/proc/sched_setaffinity.c index 5e494ee98..79ab9fcfe 100644 --- a/libc/proc/sched_setaffinity.c +++ b/libc/proc/sched_setaffinity.c @@ -24,7 +24,7 @@ #include "libc/nt/errors.h" #include "libc/nt/process.h" #include "libc/nt/runtime.h" -#include "libc/proc/proc.internal.h" +#include "libc/proc/proc.h" #include "libc/sysv/errfuns.h" static dontinline textwindows int sys_sched_setaffinity_nt( diff --git a/libc/proc/setpriority-nt.c b/libc/proc/setpriority-nt.c index 837bd8743..3aeb56dfc 100644 --- a/libc/proc/setpriority-nt.c +++ b/libc/proc/setpriority-nt.c @@ -23,7 +23,7 @@ #include "libc/nt/errors.h" #include "libc/nt/process.h" #include "libc/nt/runtime.h" -#include "libc/proc/proc.internal.h" +#include "libc/proc/proc.h" #include "libc/sysv/consts/prio.h" #include "libc/sysv/errfuns.h" diff --git a/libc/proc/times.c b/libc/proc/times.c index 1538e1a9b..28973c964 100644 --- a/libc/proc/times.c +++ b/libc/proc/times.c @@ -16,55 +16,38 @@ │ 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/rusage.h" +#include "libc/calls/struct/timespec.h" #include "libc/calls/struct/timeval.h" #include "libc/calls/struct/tms.h" -#include "libc/calls/syscall_support-nt.internal.h" -#include "libc/dce.h" -#include "libc/fmt/wintime.internal.h" -#include "libc/nt/accounting.h" -#include "libc/nt/runtime.h" #include "libc/runtime/clktck.h" -#include "libc/runtime/sysconf.h" +#include "libc/sysv/consts/clock.h" #include "libc/sysv/consts/rusage.h" -#include "libc/time.h" -static dontinline long ConvertMicros(struct timeval tv) { +static long MicrosToTicks(struct timeval tv) { return tv.tv_sec * CLK_TCK + tv.tv_usec / (1000000 / CLK_TCK); } -static dontinline long times2(struct tms *out_times, struct rusage *ru) { - struct timeval tv; - struct NtFileTime CreationTime, ExitTime, KernelTime, UserTime; - if (!IsWindows()) { - if (getrusage(RUSAGE_SELF, ru) == -1) - return -1; - out_times->tms_utime = ConvertMicros(ru->ru_utime); - out_times->tms_stime = ConvertMicros(ru->ru_stime); - if (getrusage(RUSAGE_CHILDREN, ru) == -1) - return -1; - out_times->tms_cutime = ConvertMicros(ru->ru_utime); - out_times->tms_cstime = ConvertMicros(ru->ru_stime); - } else { - if (!GetProcessTimes(GetCurrentProcess(), &CreationTime, &ExitTime, - &KernelTime, &UserTime)) { - return __winerr(); - } - out_times->tms_utime = ReadFileTime(UserTime); - out_times->tms_stime = ReadFileTime(KernelTime); - out_times->tms_cutime = 0; - out_times->tms_cstime = 0; - } - if (gettimeofday(&tv, NULL) == -1) - return -1; - return ConvertMicros(tv); +static long NanosToTicks(struct timespec ts) { + return ts.tv_sec * CLK_TCK + ts.tv_nsec / (1000000000 / CLK_TCK); } /** * Returns accounting data for process on time-sharing system. + * @return number of `CLK_TCK` from `CLOCK_BOOTTIME` epoch */ long times(struct tms *out_times) { - struct rusage ru; - return times2(out_times, &ru); + struct timespec bt; + struct rusage rus, ruc; + if (getrusage(RUSAGE_SELF, &rus)) + return -1; + if (getrusage(RUSAGE_CHILDREN, &ruc)) + return -1; + if (clock_gettime(CLOCK_BOOTTIME, &bt)) + return -1; + out_times->tms_utime = MicrosToTicks(rus.ru_utime); + out_times->tms_stime = MicrosToTicks(rus.ru_stime); + out_times->tms_cutime = MicrosToTicks(ruc.ru_utime); + out_times->tms_cstime = MicrosToTicks(ruc.ru_stime); + return NanosToTicks(bt); } diff --git a/libc/proc/vfork.S b/libc/proc/vfork.S index e9b791127..3f87d74e1 100644 --- a/libc/proc/vfork.S +++ b/libc/proc/vfork.S @@ -121,7 +121,7 @@ vfork: // } else { // __get_tls()->tib_flags &= ~TIB_FLAG_VFORKED; // } - sub x1,x28,#512 // sizeof(CosmoTib) + sub x1,x28,#1024 // sizeof(CosmoTib) ldr x2,[x1,64] cbnz x0,2f orr x2,x2,#TIB_FLAG_VFORKED diff --git a/libc/proc/wait4-nt.c b/libc/proc/wait4-nt.c index 0da6a31d3..fe8e0d85d 100644 --- a/libc/proc/wait4-nt.c +++ b/libc/proc/wait4-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/internal.h" #include "libc/calls/sig.internal.h" #include "libc/calls/struct/sigset.h" @@ -27,25 +28,22 @@ #include "libc/nt/events.h" #include "libc/nt/runtime.h" #include "libc/nt/synchronization.h" -#include "libc/proc/proc.internal.h" +#include "libc/proc/proc.h" #include "libc/sysv/consts/sicode.h" #include "libc/sysv/consts/sig.h" #include "libc/sysv/consts/w.h" #include "libc/sysv/errfuns.h" #ifdef __x86_64__ -static textwindows int __proc_reap(struct Proc *pr, int *wstatus, +textwindows static int __proc_reap(struct Proc *pr, int *wstatus, struct rusage *opt_out_rusage) { - if (wstatus) { + if (wstatus) *wstatus = pr->wstatus; - } - if (opt_out_rusage) { + if (opt_out_rusage) *opt_out_rusage = pr->ru; - } dll_remove(&__proc.zombies, &pr->elem); - if (dll_is_empty(__proc.zombies)) { + if (dll_is_empty(__proc.zombies)) ResetEvent(__proc.haszombies); - } if (pr->waiters) { pr->status = PROC_UNDEAD; dll_make_first(&__proc.undead, &pr->elem); @@ -56,19 +54,18 @@ static textwindows int __proc_reap(struct Proc *pr, int *wstatus, return pr->pid; } -static textwindows int __proc_check(int pid, int *wstatus, +textwindows static int __proc_check(int pid, int *wstatus, struct rusage *opt_out_rusage) { struct Dll *e; for (e = dll_first(__proc.zombies); e; e = dll_next(__proc.zombies, e)) { struct Proc *pr = PROC_CONTAINER(e); - if (pid == -1 || pid == pr->pid) { + if (pid == -1 || pid == pr->pid) return __proc_reap(pr, wstatus, opt_out_rusage); - } } return 0; } -static textwindows int __proc_wait(int pid, int *wstatus, int options, +textwindows static int __proc_wait(int pid, int *wstatus, int options, struct rusage *rusage, sigset_t waitmask) { for (;;) { @@ -88,18 +85,20 @@ static textwindows int __proc_wait(int pid, int *wstatus, int options, __proc_lock(); CheckForZombies: int rc = __proc_check(pid, wstatus, rusage); + + // if there's no zombies left + // check if there's any living processes + if (!rc && dll_is_empty(__proc.list)) { + __proc_unlock(); + return echild(); + } + + // otherwise return zombie or zero if (rc || (options & WNOHANG)) { __proc_unlock(); return rc; } - // there's no zombies left - // check if there's any living processes - if (dll_is_empty(__proc.list)) { - __proc_unlock(); - return echild(); - } - // get appropriate wait object // register ourself as waiting struct Proc *pr = 0; @@ -132,14 +131,18 @@ static textwindows int __proc_wait(int pid, int *wstatus, int options, // perform blocking operation uint32_t wi; uintptr_t event; - struct PosixThread *pt = _pthread_self(); - pt->pt_blkmask = waitmask; - pt->pt_event = event = CreateEvent(0, 0, 0, 0); - atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_EVENT, - memory_order_release); - wi = WaitForMultipleObjects(2, (intptr_t[2]){hWaitObject, event}, 0, -1u); - atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release); - CloseHandle(event); + if ((event = CreateEvent(0, 0, 0, 0))) { + struct PosixThread *pt = _pthread_self(); + pt->pt_event = event; + pt->pt_blkmask = waitmask; + atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_EVENT, + memory_order_release); + wi = WaitForMultipleObjects(2, (intptr_t[2]){hWaitObject, event}, 0, -1u); + atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release); + CloseHandle(event); + } else { + wi = -1u; + } // log warning if handle unexpectedly closed if (wi & kNtWaitAbandoned) { @@ -157,15 +160,14 @@ static textwindows int __proc_wait(int pid, int *wstatus, int options, // check if killed or win32 error if (wi) { if (pr) { - if (!--pr->waiters && pr->status == PROC_UNDEAD) { + if (!--pr->waiters && pr->status == PROC_UNDEAD) __proc_free(pr); - } } else { --__proc.waiters; } __proc_unlock(); if (wi == 1) { - // __sig_cancel() woke our semaphore + // __sig_wake() woke our semaphore continue; } else { // neither posix or win32 define i/o error conditions for @@ -176,17 +178,15 @@ static textwindows int __proc_wait(int pid, int *wstatus, int options, // handle process exit notification --pr->waiters; - if (pr->status == PROC_ALIVE) { + if (pr->status == PROC_ALIVE) __proc_harvest(pr, true); - } switch (pr->status) { case PROC_ALIVE: // exit caused by execve() reparenting - __proc_unlock(); - if (!pr->waiters) { + if (!pr->waiters) // avoid deadlock that could theoretically happen SetEvent(__proc.onbirth); - } + __proc_unlock(); break; case PROC_ZOMBIE: // exit happened and we're the first to know @@ -195,9 +195,8 @@ static textwindows int __proc_wait(int pid, int *wstatus, int options, return rc; case PROC_UNDEAD: // exit happened but another thread waited first - if (!pr->waiters) { + if (!pr->waiters) __proc_free(pr); - } __proc_unlock(); return echild(); default: diff --git a/libc/proc/wait4.c b/libc/proc/wait4.c index 056e9b371..0db1f4a81 100644 --- a/libc/proc/wait4.c +++ b/libc/proc/wait4.c @@ -21,7 +21,7 @@ #include "libc/calls/struct/rusage.internal.h" #include "libc/dce.h" #include "libc/intrin/strace.h" -#include "libc/proc/proc.internal.h" +#include "libc/proc/proc.h" #include "libc/sysv/errfuns.h" /** diff --git a/libc/runtime/at_quick_exit.c b/libc/runtime/at_quick_exit.c index 26c1c86af..c9aa389b9 100644 --- a/libc/runtime/at_quick_exit.c +++ b/libc/runtime/at_quick_exit.c @@ -16,35 +16,32 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/atomic.h" +#include "libc/intrin/cxaatexit.h" #include "libc/macros.h" -#include "libc/runtime/runtime.h" -#include "libc/thread/thread.h" +#include "libc/stdlib.h" static void (*funcs[32])(void); static int count; -static pthread_spinlock_t lock; -pthread_spinlock_t *const __at_quick_exit_lockptr = &lock; void __funcs_on_quick_exit(void) { void (*func)(void); - pthread_spin_lock(&lock); + __cxa_lock(); while (count) { func = funcs[--count]; - pthread_spin_unlock(&lock); + __cxa_unlock(); func(); - pthread_spin_lock(&lock); + __cxa_lock(); } } int at_quick_exit(void func(void)) { int res = 0; - pthread_spin_lock(&lock); + __cxa_lock(); if (count == ARRAYLEN(funcs)) { res = -1; } else { funcs[count++] = func; } - pthread_spin_unlock(&lock); + __cxa_unlock(); return res; } diff --git a/libc/runtime/clone-linux.S b/libc/runtime/clone-linux.S index 8ac10c89b..909d525fe 100644 --- a/libc/runtime/clone-linux.S +++ b/libc/runtime/clone-linux.S @@ -26,54 +26,46 @@ // @param rdx x2 is ptid // @param rcx x3 is ctid // @param r8 x4 is tls -// @param r9 x5 is func(void*,int)→int +// @param r9 x5 is func(void*)→int // @param 8(rsp) x6 is arg // @return tid of child on success, or -errno on error sys_clone_linux: + beg + pro #ifdef __x86_64__ - push %rbp - mov %rsp,%rbp - push %rbx + cpush %rbx mov %rcx,%r10 mov 16(%rbp),%rbx mov $56,%eax // __NR_clone syscall test %rax,%rax jz 2f -0: pop %rbx - pop %rbp +0: cpop %rbx + epi ret 2: xor %ebp,%ebp // child thread mov %rbx,%rdi // arg - mov %r10,%r15 // experiment - mov (%r10),%esi // tid call *%r9 // func(arg,tid) xchg %eax,%edi // func(arg,tid) → exitcode - mov (%r15),%eax // experiment - test %eax,%eax // experiment - jz 1f // experiment mov $60,%eax // __NR_exit(exitcode) syscall -1: hlt // ctid was corrupted by program! #elif defined(__aarch64__) - stp x29,x30,[sp,#-16]! - mov x29,sp mov x8,x3 // swap x3 and x4 mov x3,x4 // swap x3 and x4 mov x4,x8 // swap x3 and x4 mov x8,#220 // __NR_clone svc #0 cbz x0,2f - ldp x29,x30,[sp],#16 + epi ret 2: mov x29,#0 // wipe backtrace mov x28,x3 // set cosmo tls mov x0,x6 // child thread - ldr w1,[x4] // arg2 = *ctid blr x5 mov x8,#93 // __NR_exit svc #0 #else #error "unsupported architecture" #endif + end .endfn sys_clone_linux,globl,hidden diff --git a/libc/runtime/clone.c b/libc/runtime/clone.c index 3f2f822dd..c2325896e 100644 --- a/libc/runtime/clone.c +++ b/libc/runtime/clone.c @@ -16,49 +16,27 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/sysv/consts/clone.h" -#include "libc/assert.h" #include "libc/atomic.h" -#include "libc/calls/calls.h" #include "libc/calls/state.internal.h" -#include "libc/calls/struct/sigset.h" #include "libc/calls/struct/ucontext-netbsd.internal.h" -#include "libc/calls/syscall-sysv.internal.h" -#include "libc/calls/wincrash.internal.h" #include "libc/dce.h" -#include "libc/errno.h" +#include "libc/intrin/asmflag.h" #include "libc/intrin/atomic.h" -#include "libc/intrin/describeflags.h" #include "libc/intrin/ulock.h" -#include "libc/intrin/weaken.h" #include "libc/limits.h" -#include "libc/macros.h" #include "libc/mem/alloca.h" #include "libc/nt/enum/processcreationflags.h" #include "libc/nt/runtime.h" -#include "libc/nt/signals.h" #include "libc/nt/synchronization.h" #include "libc/nt/thread.h" #include "libc/nt/thunk/msabi.h" -#include "libc/runtime/internal.h" #include "libc/runtime/runtime.h" -#include "libc/runtime/stack.h" #include "libc/runtime/syslib.internal.h" #include "libc/sock/internal.h" -#include "libc/stdalign.h" -#include "libc/stdio/sysparam.h" -#include "libc/str/str.h" #include "libc/sysv/consts/arch.h" -#include "libc/sysv/consts/clone.h" -#include "libc/sysv/consts/futex.h" -#include "libc/sysv/consts/nr.h" -#include "libc/sysv/consts/nrlinux.h" -#include "libc/sysv/errfuns.h" #include "libc/thread/freebsd.internal.h" #include "libc/thread/openbsd.internal.h" -#include "libc/thread/thread.h" -#include "libc/thread/tls.h" -#include "libc/thread/tls2.internal.h" +#include "libc/thread/posixthread.internal.h" #include "libc/thread/xnu.internal.h" #define kMaxThreadIds 32768 @@ -78,85 +56,73 @@ #define LWP_SUSPENDED 0x00000080 struct CloneArgs { - alignas(16) union { - struct { - atomic_int tid; - int this; - }; + union { + long sp; int64_t tid64; }; atomic_int *ptid; atomic_int *ctid; - atomic_int *ztid; char *tls; - int (*func)(void *, int); + int (*func)(void *); void *arg; - long sp; }; int sys_set_tls(uintptr_t, void *); -int __stack_call(void *, int, long, long, int (*)(void *, int), long); - -static long AlignStack(long sp, char *stk, long stksz, int mal) { - return sp & -mal; -} +int __stack_call(void *, int, long, long, int (*)(void *), long); #ifdef __x86_64__ //////////////////////////////////////////////////////////////////////////////// // THE NEW TECHNOLOGY -__msabi extern typeof(TlsSetValue) *const __imp_TlsSetValue; __msabi extern typeof(ExitThread) *const __imp_ExitThread; +__msabi extern typeof(GetCurrentThreadId) *const __imp_GetCurrentThreadId; __msabi extern typeof(WakeByAddressAll) *const __imp_WakeByAddressAll; -static textwindows dontinstrument wontreturn void // +textwindows dontinstrument wontreturn static void // WinThreadEntry(int rdi, // rcx int rsi, // rdx int rdx, // r8 struct CloneArgs *wt) { // r9 - int rc; - if (wt->tls) - __set_tls_win32(wt->tls); - *wt->ctid = GetCurrentThreadId(); - rc = __stack_call(wt->arg, wt->tid, 0, 0, wt->func, wt->sp); + __set_tls_win32(wt->tls); + int tid = __imp_GetCurrentThreadId(); + atomic_int *ctid = wt->ctid; + atomic_init(ctid, tid); + atomic_init(wt->ptid, tid); + int rc = __stack_call(wt->arg, tid, 0, 0, wt->func, wt->sp); // we can now clear ctid directly since we're no longer using our own // stack memory, which can now be safely free'd by the parent thread. - *wt->ztid = 0; - __imp_WakeByAddressAll(wt->ztid); + atomic_store_explicit(ctid, 0, memory_order_release); + __imp_WakeByAddressAll(ctid); // since we didn't indirect this function through NT2SYSV() it's not // safe to simply return, and as such, we need ExitThread(). __imp_ExitThread(rc); __builtin_unreachable(); } -static textwindows errno_t CloneWindows(int (*func)(void *, int), char *stk, - size_t stksz, int flags, void *arg, - void *tls, atomic_int *ptid, - atomic_int *ctid) { +textwindows static errno_t CloneWindows(int (*func)(void *), char *stk, + size_t stksz, void *arg, void *tls, + atomic_int *ptid, atomic_int *ctid) { long sp; int64_t h; + intptr_t tip; uint32_t utid; struct CloneArgs *wt; - sp = (intptr_t)stk + stksz; - sp = AlignStack(sp, stk, stksz, 16); + sp = tip = (intptr_t)stk + stksz; sp -= sizeof(struct CloneArgs); sp &= -alignof(struct CloneArgs); wt = (struct CloneArgs *)sp; - wt->ctid = flags & CLONE_CHILD_SETTID ? ctid : &wt->tid; - wt->ztid = flags & CLONE_CHILD_CLEARTID ? ctid : &wt->tid; + wt->ctid = ctid; + wt->ptid = ptid; wt->func = func; wt->arg = arg; - wt->tls = flags & CLONE_SETTLS ? tls : 0; - wt->sp = sp; - if ((h = CreateThread(&kNtIsInheritable, 65536, (void *)WinThreadEntry, wt, + wt->tls = tls; + wt->sp = tip & -16; + if ((h = CreateThread(0, 65536, (void *)WinThreadEntry, wt, kNtStackSizeParamIsAReservation, &utid))) { - if (flags & CLONE_PARENT_SETTID) - *ptid = utid; - if (flags & CLONE_SETTLS) { - struct CosmoTib *tib = tls; - atomic_store_explicit(&tib->tib_syshand, h, memory_order_release); - } + atomic_init(ptid, utid); + struct CosmoTib *tib = tls; + atomic_store_explicit(&tib->tib_syshand, h, memory_order_release); return 0; } else { return __dos2errno(GetLastError()); @@ -180,36 +146,33 @@ asm("XnuThreadThunk:\n\t" ".size\tXnuThreadThunk,.-XnuThreadThunk"); __attribute__((__used__)) -static wontreturn void -XnuThreadMain(void *pthread, // rdi - int tid, // rsi - int (*func)(void *arg, int tid), // rdx - void *arg, // rcx - struct CloneArgs *wt, // r8 - unsigned xnuflags) { // r9 +dontinstrument wontreturn static void +XnuThreadMain(void *pthread, // rdi + int tid, // rsi + int (*func)(void *arg), // rdx + void *arg, // rcx + struct CloneArgs *wt, // r8 + unsigned xnuflags) { // r9 + atomic_init(wt->ctid, tid); + atomic_init(wt->ptid, tid); + + // XNU uses the same 0x30 offset as the WIN32 TIB x64. They told the + // Go team at Google that they Apply stands by our ability to use it + // https://github.com/golang/go/issues/23617#issuecomment-376662373 int ax; - wt->tid = tid; - *wt->ctid = tid; - *wt->ptid = tid; + asm volatile("syscall" + : "=a"(ax) + : "0"(__NR_thread_fast_set_cthread_self), "D"(wt->tls - 0x30) + : "rcx", "rdx", "r8", "r9", "r10", "r11", "memory", "cc"); - if (wt->tls) { - // XNU uses the same 0x30 offset as the WIN32 TIB x64. They told the - // Go team at Google that they Apply stands by our ability to use it - // https://github.com/golang/go/issues/23617#issuecomment-376662373 - asm volatile("syscall" - : "=a"(ax) - : "0"(__NR_thread_fast_set_cthread_self), "D"(wt->tls - 0x30) - : "rcx", "rdx", "r8", "r9", "r10", "r11", "memory", "cc"); - } - - func(arg, tid); + func(arg); // we no longer use the stack after this point // %rax = int bsdthread_terminate(%rdi = void *stackaddr, // %rsi = size_t freesize, // %rdx = uint32_t port, // %r10 = uint32_t sem); - asm volatile("movl\t$0,(%%rsi)\n\t" // *wt->ztid = 0 + asm volatile("movl\t$0,(%%rsi)\n\t" // *wt->ctid = 0 "mov\t$0x101,%%edi\n\t" // wake all "xor\t%%edx,%%edx\n\t" // wake_value "mov\t$0x02000204,%%eax\n\t" // ulock_wake() @@ -221,19 +184,18 @@ XnuThreadMain(void *pthread, // rdi "mov\t$0x02000169,%%eax\n\t" // bsdthread_terminate() "syscall" : /* no outputs */ - : "S"(wt->ztid) + : "S"(wt->ctid) : "rax", "rcx", "r10", "r11", "memory"); __builtin_unreachable(); } -static errno_t CloneXnu(int (*fn)(void *), char *stk, size_t stksz, int flags, - void *arg, void *tls, atomic_int *ptid, - atomic_int *ctid) { +static errno_t CloneXnu(int (*fn)(void *), char *stk, size_t stksz, void *arg, + void *tls, atomic_int *ptid, atomic_int *ctid) { // perform this weird mandatory system call once static bool once; if (!once) { - npassert(sys_bsdthread_register(XnuThreadThunk, 0, 0, 0, 0, 0, 0) != -1); + sys_bsdthread_register(XnuThreadThunk, 0, 0, 0, 0, 0, 0); once = true; } @@ -241,16 +203,15 @@ static errno_t CloneXnu(int (*fn)(void *), char *stk, size_t stksz, int flags, long sp; struct CloneArgs *wt; sp = (intptr_t)stk + stksz; - sp = AlignStack(sp, stk, stksz, 16); sp -= sizeof(struct CloneArgs); sp &= -alignof(struct CloneArgs); wt = (struct CloneArgs *)sp; + sp &= -16; // pass parameters to new thread via xnu - wt->ptid = flags & CLONE_PARENT_SETTID ? ptid : &wt->tid; - wt->ctid = flags & CLONE_CHILD_SETTID ? ctid : &wt->tid; - wt->ztid = flags & CLONE_CHILD_CLEARTID ? ctid : &wt->tid; - wt->tls = flags & CLONE_SETTLS ? tls : 0; + wt->ctid = ctid; + wt->ptid = ptid; + wt->tls = tls; return sys_clone_xnu(fn, arg, wt, 0, PTHREAD_START_CUSTOM_XNU); } @@ -259,26 +220,27 @@ static errno_t CloneXnu(int (*fn)(void *), char *stk, size_t stksz, int flags, // we can't use address sanitizer because: // 1. __asan_handle_no_return wipes stack [todo?] -static wontreturn void OpenbsdThreadMain(void *p) { +relegated dontinstrument wontreturn static void OpenbsdThreadMain(void *p) { struct CloneArgs *wt = p; - *wt->ctid = wt->tid; - wt->func(wt->arg, wt->tid); - asm volatile("mov\t%2,%%rsp\n\t" // so syscall can validate stack exists - "movl\t$0,(%%rdi)\n\t" // *wt->ztid = 0 (old stack now free'd) + int tid = atomic_load_explicit(wt->ctid, memory_order_relaxed); + atomic_init(wt->ptid, tid); + wt->func(wt->arg); + asm volatile("mov\t%1,%%rsp\n\t" // so syscall can validate stack exists + "movl\t$0,(%2)\n\t" // *wt->ctid = 0 (old stack now free'd) "syscall\n\t" // futex(int*, op, val) will wake wait0 "xor\t%%edi,%%edi\n\t" // so kernel doesn't write to old stack "mov\t$302,%%eax\n\t" // __threxit(int *notdead) doesn't wake "syscall" - : "=m"(*wt->ztid) - : "a"(83), "m"(__oldstack), "D"(wt->ztid), + : /* no outputs */ + : "a"(83), "m"(__oldstack), "D"(wt->ctid), "S"(2 /* FUTEX_WAKE */), "d"(INT_MAX) : "rcx", "r11", "memory"); __builtin_unreachable(); } -static errno_t CloneOpenbsd(int (*func)(void *, int), char *stk, size_t stksz, - int flags, void *arg, void *tls, atomic_int *ptid, - atomic_int *ctid) { +relegated static errno_t CloneOpenbsd(int (*func)(void *), char *stk, + size_t stksz, void *arg, void *tls, + atomic_int *ptid, atomic_int *ctid) { int rc; intptr_t sp; struct __tfork *tf; @@ -290,19 +252,18 @@ static errno_t CloneOpenbsd(int (*func)(void *, int), char *stk, size_t stksz, sp -= sizeof(struct CloneArgs); sp &= -alignof(struct CloneArgs); wt = (struct CloneArgs *)sp; - sp = AlignStack(sp, stk, stksz, 16); - wt->ctid = flags & CLONE_CHILD_SETTID ? ctid : &wt->tid; - wt->ztid = flags & CLONE_CHILD_CLEARTID ? ctid : &wt->tid; + sp &= -16; + sp -= 8; + *(intptr_t *)sp = (intptr_t)CloneOpenbsd + 1; + wt->ctid = ctid; + wt->ptid = ptid; wt->arg = arg; wt->func = func; - tf->tf_stack = (char *)sp - 8; - tf->tf_tcb = flags & CLONE_SETTLS ? tls : 0; - tf->tf_tid = &wt->tid; + tf->tf_stack = (char *)sp; + tf->tf_tcb = tls; + tf->tf_tid = ctid; if ((rc = __tfork_thread(tf, sizeof(*tf), OpenbsdThreadMain, wt)) >= 0) { - npassert(rc); - if (flags & CLONE_PARENT_SETTID) { - *ptid = rc; - } + atomic_init(ptid, rc); return 0; } else { return -rc; @@ -312,35 +273,35 @@ static errno_t CloneOpenbsd(int (*func)(void *, int), char *stk, size_t stksz, //////////////////////////////////////////////////////////////////////////////// // NET BESIYATA DISHMAYA -static wontreturn void NetbsdThreadMain(void *arg, // rdi - int (*func)(void *, int), // rsi - int *tid, // rdx - atomic_int *ctid, // rcx - int *ztid) { // r9 - int ax, dx; - // TODO(jart): Why are we seeing flakes where *tid is zero? - // ax = *tid; - ax = sys_gettid(); - *ctid = ax; - func(arg, ax); +wontreturn dontinstrument static void NetbsdThreadMain( + void *arg, // rdi + int (*func)(void *), // rsi + atomic_int *ctid, // rdx + atomic_int *ptid) { // rcx + int ax; + asm("syscall" + : "=a"(ax) // man says always succeeds + : "0"(311) // _lwp_self() + : "rcx", "rdx", "r8", "r9", "r10", "r11", "memory", "cc"); + atomic_init(ctid, ax); + atomic_init(ptid, ax); + func(arg); // we no longer use the stack after this point // %eax = int __lwp_exit(void); - asm volatile("movl\t$0,%2\n\t" // *wt->ztid = 0 - "syscall" // __lwp_exit() - : "=a"(ax), "=d"(dx), "=m"(*ztid) - : "0"(310) + asm volatile("movl\t$0,(%2)\n\t" // *ztid = 0 + "syscall" // __lwp_exit() + : "=a"(ax) + : "0"(310), "r"(ctid) : "rcx", "r11", "memory"); __builtin_unreachable(); } -static int CloneNetbsd(int (*func)(void *, int), char *stk, size_t stksz, - int flags, void *arg, void *tls, atomic_int *ptid, - atomic_int *ctid) { +static int CloneNetbsd(int (*func)(void *), char *stk, size_t stksz, void *arg, + void *tls, atomic_int *ptid, atomic_int *ctid) { // NetBSD has its own clone() and it works, but it's technically a // second-class API, intended to help Linux folks migrate to this. int ax; bool failed; - atomic_int *tid; intptr_t dx, sp; static bool once; struct ucontext_netbsd *ctx; @@ -352,19 +313,12 @@ static int CloneNetbsd(int (*func)(void *, int), char *stk, size_t stksz, : CFLAG_CONSTRAINT(failed), "=a"(ax) : "1"(__NR_getcontext_netbsd), "D"(&netbsd_clone_template) : "rcx", "rdx", "r8", "r9", "r10", "r11", "memory"); - npassert(!failed); once = true; } sp = (intptr_t)stk + stksz; - // allocate memory for tid - sp -= sizeof(atomic_int); - sp = sp & -alignof(atomic_int); - tid = (atomic_int *)sp; - *tid = 0; - // align the stack - sp = AlignStack(sp, stk, stksz, 16); + sp &= -16; // simulate call to misalign stack and ensure backtrace looks good sp -= 8; @@ -372,8 +326,7 @@ static int CloneNetbsd(int (*func)(void *, int), char *stk, size_t stksz, // place the giant 784 byte ucontext structure in the red zone! // it only has to live long enough for the thread to come alive - ctx = (struct ucontext_netbsd *)((sp - sizeof(struct ucontext_netbsd)) & - -alignof(struct ucontext_netbsd)); + ctx = (struct ucontext_netbsd *)((sp - sizeof(struct ucontext_netbsd)) & -64); // pass parameters in process state memcpy(ctx, &netbsd_clone_template, sizeof(*ctx)); @@ -383,28 +336,23 @@ static int CloneNetbsd(int (*func)(void *, int), char *stk, size_t stksz, ctx->uc_mcontext.rip = (intptr_t)NetbsdThreadMain; ctx->uc_mcontext.rdi = (intptr_t)arg; ctx->uc_mcontext.rsi = (intptr_t)func; - ctx->uc_mcontext.rdx = (intptr_t)tid; - ctx->uc_mcontext.rcx = (intptr_t)(flags & CLONE_CHILD_SETTID ? ctid : tid); - ctx->uc_mcontext.r8 = (intptr_t)(flags & CLONE_CHILD_CLEARTID ? ctid : tid); + ctx->uc_mcontext.rdx = (intptr_t)ctid; + ctx->uc_mcontext.rcx = (intptr_t)ptid; ctx->uc_flags |= _UC_STACK; ctx->uc_stack.ss_sp = stk; ctx->uc_stack.ss_size = stksz; ctx->uc_stack.ss_flags = 0; - if (flags & CLONE_SETTLS) { - ctx->uc_flags |= _UC_TLSBASE; - ctx->uc_mcontext._mc_tlsbase = (intptr_t)tls; - } + ctx->uc_flags |= _UC_TLSBASE; + ctx->uc_mcontext._mc_tlsbase = (intptr_t)tls; // perform the system call + int tid = 0; asm volatile(CFLAG_ASM("syscall") : CFLAG_CONSTRAINT(failed), "=a"(ax), "=d"(dx) - : "1"(__NR__lwp_create), "D"(ctx), "S"(LWP_DETACHED), "2"(tid) + : "1"(__NR__lwp_create), "D"(ctx), "S"(LWP_DETACHED), "2"(&tid) : "rcx", "r8", "r9", "r10", "r11", "memory"); if (!failed) { - npassert(*tid); - if (flags & CLONE_PARENT_SETTID) { - *ptid = *tid; - } + atomic_init(ptid, tid); return 0; } else { return ax; @@ -416,41 +364,42 @@ static int CloneNetbsd(int (*func)(void *, int), char *stk, size_t stksz, //////////////////////////////////////////////////////////////////////////////// // FREE BESIYATA DISHMAYA -static wontreturn void FreebsdThreadMain(void *p) { +wontreturn dontinstrument static void FreebsdThreadMain(void *p) { struct CloneArgs *wt = p; #ifdef __aarch64__ asm volatile("mov\tx28,%0" : /* no outputs */ : "r"(wt->tls)); #elif defined(__x86_64__) sys_set_tls(AMD64_SET_GSBASE, wt->tls); #endif - *wt->ctid = wt->tid; - wt->func(wt->arg, wt->tid); + atomic_init(wt->ctid, wt->tid64); + atomic_init(wt->ptid, wt->tid64); + wt->func(wt->arg); // we no longer use the stack after this point // void thr_exit(%rdi = long *state); #ifdef __x86_64__ - asm volatile("movl\t$0,%0\n\t" // *wt->ztid = 0 - "syscall\n\t" // _umtx_op(wt->ztid, WAKE, INT_MAX) + asm volatile("movl\t$0,%0\n\t" // *wt->ctid = 0 + "syscall\n\t" // _umtx_op(wt->ctid, WAKE, INT_MAX) "movl\t$431,%%eax\n\t" // thr_exit(long *nonzeroes_and_wake) "xor\t%%edi,%%edi\n\t" // sad we can't use this free futex op "syscall\n\t" // thr_exit() fails if thread is orphaned "movl\t$1,%%eax\n\t" // _exit() "syscall" // - : "=m"(*wt->ztid) - : "a"(454), "D"(wt->ztid), "S"(UMTX_OP_WAKE), "d"(INT_MAX) + : "=m"(*wt->ctid) + : "a"(454), "D"(wt->ctid), "S"(UMTX_OP_WAKE), "d"(INT_MAX) : "rcx", "r8", "r9", "r10", "r11", "memory"); #elif defined(__aarch64__) - register long x0 asm("x0") = (long)wt->ztid; + register long x0 asm("x0") = (long)wt->ctid; register long x1 asm("x1") = UMTX_OP_WAKE; register long x2 asm("x2") = INT_MAX; register long x8 asm("x8") = 454; // _umtx_op - asm volatile("str\twzr,%0\n\t" // *wt->ztid = 0 - "svc\t0\n\t" // _umtx_op(wt->ztid, WAKE, INT_MAX) + asm volatile("str\twzr,%0\n\t" // *wt->ctid = 0 + "svc\t0\n\t" // _umtx_op(wt->ctid, WAKE, INT_MAX) "mov\tx0,#0\n\t" // arg0 = 0 "mov\tx8,#431\n\t" // thr_exit "svc\t0\n\t" // thr_exit(long *nonzeroes_and_wake = 0) "mov\tx8,#1\n\t" // _exit "svc\t0" // _exit(long *nonzeroes_and_wake = 0) - : "=m"(*wt->ztid) + : "=m"(*wt->ctid) : "r"(x0), "r"(x1), "r"(x2), "r"(x8)); #else #error "unsupported architecture" @@ -458,19 +407,19 @@ static wontreturn void FreebsdThreadMain(void *p) { __builtin_unreachable(); } -static errno_t CloneFreebsd(int (*func)(void *, int), char *stk, size_t stksz, - int flags, void *arg, void *tls, atomic_int *ptid, +static errno_t CloneFreebsd(int (*func)(void *), char *stk, size_t stksz, + void *arg, void *tls, atomic_int *ptid, atomic_int *ctid) { long sp; - int64_t tid; + int64_t tid64; struct CloneArgs *wt; sp = (intptr_t)stk + stksz; sp -= sizeof(struct CloneArgs); sp &= -alignof(struct CloneArgs); wt = (struct CloneArgs *)sp; - sp = AlignStack(sp, stk, stksz, 16); - wt->ctid = flags & CLONE_CHILD_SETTID ? ctid : &wt->tid; - wt->ztid = flags & CLONE_CHILD_CLEARTID ? ctid : &wt->tid; + sp &= -16; + wt->ctid = ctid; + wt->ptid = ptid; wt->tls = tls; wt->func = func; wt->arg = arg; @@ -479,10 +428,10 @@ static errno_t CloneFreebsd(int (*func)(void *, int), char *stk, size_t stksz, .arg = wt, .stack_base = stk, .stack_size = sp - (long)stk, - .tls_base = flags & CLONE_SETTLS ? tls : 0, + .tls_base = tls, .tls_size = 64, .child_tid = &wt->tid64, - .parent_tid = &tid, + .parent_tid = &tid64, }; #ifdef __x86_64__ int ax; @@ -503,8 +452,7 @@ static errno_t CloneFreebsd(int (*func)(void *, int), char *stk, size_t stksz, #else #error "unsupported architecture" #endif - if (flags & CLONE_PARENT_SETTID) - *ptid = tid; + atomic_init(ptid, tid64); return 0; } @@ -513,57 +461,59 @@ static errno_t CloneFreebsd(int (*func)(void *, int), char *stk, size_t stksz, //////////////////////////////////////////////////////////////////////////////// // APPLE SILICON -static void *SiliconThreadMain(void *arg) { +dontinstrument static void *SiliconThreadMain(void *arg) { struct CloneArgs *wt = arg; + atomic_int *ctid = wt->ctid; + int tid = atomic_load_explicit(ctid, memory_order_relaxed); asm volatile("mov\tx28,%0" : /* no outputs */ : "r"(wt->tls)); - *wt->ctid = wt->this; - __stack_call(wt->arg, wt->this, 0, 0, wt->func, wt->sp); - *wt->ztid = 0; - ulock_wake(UL_COMPARE_AND_WAIT | ULF_WAKE_ALL, wt->ztid, 0); + __stack_call(wt->arg, tid, 0, 0, wt->func, wt->sp); + atomic_store_explicit(ctid, 0, memory_order_release); + ulock_wake(UL_COMPARE_AND_WAIT | ULF_WAKE_ALL, ctid, 0); return 0; } -static errno_t CloneSilicon(int (*fn)(void *, int), char *stk, size_t stksz, - int flags, void *arg, void *tls, atomic_int *ptid, +static errno_t CloneSilicon(int (*fn)(void *), char *stk, size_t stksz, + void *arg, void *tls, atomic_int *ptid, atomic_int *ctid) { - long sp; - void *attr; - errno_t res; - unsigned tid; - pthread_t th; - size_t babystack; - struct CloneArgs *wt; + + // assign tid to new thread static atomic_uint tids; - sp = (intptr_t)stk + stksz; + unsigned tid = atomic_fetch_add_explicit(&tids, 1, memory_order_relaxed); + tid %= kMaxThreadIds; + tid += kMinThreadId; + atomic_init(ctid, tid); + atomic_init(ptid, tid); + + // pass temp data on stack + intptr_t sp, tip; + struct CloneArgs *wt; + sp = tip = (intptr_t)stk + stksz; sp -= sizeof(struct CloneArgs); sp &= -alignof(struct CloneArgs); wt = (struct CloneArgs *)sp; - sp = AlignStack(sp, stk, stksz, 16); - tid = atomic_fetch_add_explicit(&tids, 1, memory_order_acq_rel); - wt->this = tid = (tid & (kMaxThreadIds - 1)) + kMinThreadId; - wt->ctid = flags & CLONE_CHILD_SETTID ? ctid : &wt->tid; - wt->ztid = flags & CLONE_CHILD_CLEARTID ? ctid : &wt->tid; - wt->tls = flags & CLONE_SETTLS ? tls : 0; wt->func = fn; wt->arg = arg; - wt->sp = sp; - babystack = __syslib->__pthread_stack_min; + wt->tls = tls; + wt->ctid = ctid; + wt->sp = tip & -16; + + // ask apple libc to spawn thread + errno_t res; + pthread_t th; + size_t babystack = __syslib->__pthread_stack_min; #pragma GCC push_options #pragma GCC diagnostic ignored "-Walloca-larger-than=" - attr = alloca(__syslib->__sizeof_pthread_attr_t); + void *attr = alloca(__syslib->__sizeof_pthread_attr_t); #pragma GCC pop_options - unassert(!__syslib->__pthread_attr_init(attr)); - unassert(!__syslib->__pthread_attr_setguardsize(attr, 0)); - unassert(!__syslib->__pthread_attr_setstacksize(attr, babystack)); - if (!(res = __syslib->__pthread_create(&th, attr, SiliconThreadMain, wt)) && - (flags & CLONE_PARENT_SETTID)) { - *ptid = tid; - if (flags & CLONE_SETTLS) { - struct CosmoTib *tib = tls; - atomic_store_explicit(&tib[-1].tib_syshand, th, memory_order_release); - } + __syslib->__pthread_attr_init(attr); + __syslib->__pthread_attr_setguardsize(attr, 0); + __syslib->__pthread_attr_setstacksize(attr, babystack); + if (!(res = __syslib->__pthread_create(&th, attr, SiliconThreadMain, wt))) { + atomic_init(ptid, tid); + struct CosmoTib *tib = tls; + atomic_store_explicit(&tib[-1].tib_syshand, th, memory_order_release); } - unassert(!__syslib->__pthread_attr_destroy(attr)); + __syslib->__pthread_attr_destroy(attr); return res; } @@ -573,10 +523,9 @@ static errno_t CloneSilicon(int (*fn)(void *, int), char *stk, size_t stksz, // GNU/SYSTEMD struct LinuxCloneArgs { - int (*func)(void *, int); + int (*func)(void *); void *arg; char *tls; - atomic_int ctid; }; int sys_clone_linux(int flags, // rdi @@ -587,44 +536,34 @@ int sys_clone_linux(int flags, // rdi void *func, // r9 void *arg); // 8(rsp) -static int LinuxThreadEntry(void *arg, int tid) { +dontinstrument static int AmdLinuxThreadEntry(void *arg) { struct LinuxCloneArgs *wt = arg; #if defined(__x86_64__) sys_set_tls(ARCH_SET_GS, wt->tls); #endif - return wt->func(wt->arg, tid); + return wt->func(wt->arg); } -static int CloneLinux(int (*func)(void *arg, int rc), char *stk, size_t stksz, - int flags, void *arg, void *tls, atomic_int *ptid, +static int CloneLinux(int (*func)(void *), char *stk, size_t stksz, int flags, + void *arg, void *tls, atomic_int *ptid, atomic_int *ctid) { - int rc; - long sp; - struct LinuxCloneArgs *wt; - sp = (intptr_t)stk + stksz; + long sp = (intptr_t)stk + stksz; + +#if defined(__x86_64__) sp -= sizeof(struct LinuxCloneArgs); sp &= -alignof(struct LinuxCloneArgs); - wt = (struct LinuxCloneArgs *)sp; - // align the stack -#ifdef __aarch64__ - sp = AlignStack(sp, stk, stksz, 128); // for kernel <=4.6 -#else - sp = AlignStack(sp, stk, stksz, 16); + struct LinuxCloneArgs *wt = (struct LinuxCloneArgs *)sp; + sp &= -16; // align the stack + wt->arg = arg; + wt->tls = tls; + wt->func = func; + func = AmdLinuxThreadEntry; + arg = wt; +#elif defined(__aarch64__) + sp &= -128; // for kernels <=4.6 #endif -#ifdef __x86_64__ - if (flags & CLONE_SETTLS) { - flags &= ~CLONE_SETTLS; - wt->arg = arg; - wt->tls = tls; - wt->func = func; - func = LinuxThreadEntry; - arg = wt; - } -#endif - if (~flags & CLONE_CHILD_SETTID) { - flags |= CLONE_CHILD_SETTID; - ctid = &wt->ctid; - } + + int rc; if ((rc = sys_clone_linux(flags, sp, ptid, ctid, tls, func, arg)) >= 0) { // clone() is documented as setting ptid before return return 0; @@ -637,150 +576,45 @@ static int CloneLinux(int (*func)(void *arg, int rc), char *stk, size_t stksz, // COSMOPOLITAN /** - * Creates thread without malloc being linked. + * Creates thread without malloc() being linked. * - * If you use clone() you're on your own. Example: - * - * int worker(void *arg) { return 0; } - * struct CosmoTib tib = {.tib_self = &tib, .tib_tid = -1}; - * atomic_int tid; - * char *stk = NewCosmoStack(); - * clone(worker, stk, GetStackSize() - 16, - * CLONE_VM | CLONE_THREAD | CLONE_FS | CLONE_FILES | - * CLONE_SYSVSEM | CLONE_SIGHAND | CLONE_PARENT_SETTID | - * CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | CLONE_SETTLS, - * arg, &tid, &tib, &tib.tib_tid); - * while (atomic_load(&tid) == 0) sched_yield(); - * // thread is known - * while (atomic_load(&tib.tib_tid) < 0) sched_yield(); - * // thread is running - * while (atomic_load(&tib.tib_tid) > 0) sched_yield(); - * // thread has terminated - * FreeCosmoStack(stk); - * - * Threads are created in a detached manner. They currently can't be - * synchronized using wait() or posix signals. Threads created by this - * function should be synchronized using shared memory operations. - * - * Any memory that's required by this system call wrapper is allocated - * to the top of your stack. This shouldn't be more than 128 bytes. - * - * Your function is called from within the stack you specify. A return - * address is pushed onto your stack, that causes returning to jump to - * _Exit1() which terminates the thread. Even though the callback says - * it supports a return code, that'll only work on Linux and Windows. - * - * This function follows the same ABI convention as the Linux userspace - * libraries, with a few small changes. The varargs has been removed to - * help prevent broken code, and the stack size and tls size parameters - * are introduced for compatibility with FreeBSD. - * - * To keep this system call lightweight, only the thread creation use - * case is polyfilled across platforms. For example, if you want fork - * that works on OpenBSD for example, don't do it with clone(SIGCHLD) - * and please just call fork(). Even if you do that on Linux, it will - * effectively work around libc features like atfork(), so that means - * other calls like getpid() may return incorrect values. - * - * @param func is your callback function, which this wrapper requires - * not be null, otherwise EINVAL is raised. It is passed two args - * within the child thread: (1) the caller-supplied `arg` and (2) - * the new tid is always passed in the second arg for convenience - * - * @param stk points to the bottom of a caller allocated stack, which - * must be allocated via mmap() using the MAP_STACK flag, or else - * you won't get optimal performance and it won't work on OpenBSD - * - * @param stksz is the size of that stack in bytes, we recommend that - * that this be set to GetStackSize() or else memory safety tools - * like kprintf() can't do as good and quick of a job; this value - * must be 16-aligned plus it must be at least 4192 bytes in size - * and it's advised to have the bottom-most page, be a guard page - * - * @param flags which SHOULD always have all of these flags: - * - * - `CLONE_THREAD` - * - `CLONE_VM` - * - `CLONE_FS` - * - `CLONE_FILES` - * - `CLONE_SIGHAND` - * - `CLONE_SYSVSEM` - * - * This system call wrapper is intended for threads, and as such, we - * won't polyfill Linux's ability to simulate unrelated calls (e.g. - * fork, vfork) via clone() on other platforms. Please just call - * fork() and vfork() when that's what you want. - * - * Your `flags` may also optionally also additionally bitwise-OR any - * combination of the following additional flags: - * - * - `CLONE_CHILD_SETTID` must be specified if you intend to set the - * `ctid` argument, which will updated with the child tid once the - * child has started. - * - * - `CLONE_PARENT_SETTID` must be specified if you intend to set - * the `ptid` argument, and it is updated at the most opportune - * moment. On all platforms except XNU x86, this happens before - * clone() returns. But since it might not be available yet you - * need to use pthread_getunique_np() to obtain it. - * - * - `CLONE_CHILD_CLEARTID` causes `*ctid = 0` upon child thread - * termination. This is used to implement join so that the parent - * may know when it's safe to free the child's stack memory, and - * as such, is guaranteed to happen AFTER the child thread has - * either terminated or has finished using its stack memory - * - * - `CLONE_SETTLS` is needed if you intend to specify the `tls` - * argument, which after thread creation may be accessed using - * __get_tls(). Doing this means that `errno`, gettid(), etc. - * correctly work. Caveat emptor if you choose not to do this. - * - * @param arg is passed as an argument to `func` in the child thread - * @param tls may be used to set the thread local storage segment; - * this parameter is ignored if `CLONE_SETTLS` is not set - * @param ctid lets the child receive its thread id without having to - * call gettid() and is ignored if `CLONE_CHILD_SETTID` isn't set - * @return 0 on success, or errno on errno + * If you use clone() you're on your own. */ errno_t clone(void *func, void *stk, size_t stksz, int flags, void *arg, void *ptid, void *tls, void *ctid) { - int rc; + errno_t err; - if (!func) { - rc = EINVAL; - } else if (IsLinux()) { - rc = CloneLinux(func, stk, stksz, flags, arg, tls, ptid, ctid); - } else if (!IsTiny() && - (flags & ~(CLONE_SETTLS | CLONE_PARENT_SETTID | - CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID)) != - (CLONE_THREAD | CLONE_VM | CLONE_FS | CLONE_FILES | - CLONE_SIGHAND | CLONE_SYSVSEM)) { - rc = EINVAL; + atomic_fetch_add(&_pthread_count, 1); + + if (IsLinux()) { + err = CloneLinux(func, stk, stksz, flags, arg, tls, ptid, ctid); } else if (IsXnu()) { -#ifdef __x86_64__ - rc = CloneXnu(func, stk, stksz, flags, arg, tls, ptid, ctid); +#if defined(__x86_64__) + err = CloneXnu(func, stk, stksz, arg, tls, ptid, ctid); #elif defined(__aarch64__) - rc = CloneSilicon(func, stk, stksz, flags, arg, tls, ptid, ctid); + err = CloneSilicon(func, stk, stksz, arg, tls, ptid, ctid); #else #error "unsupported architecture" #endif } else if (IsFreebsd()) { - rc = CloneFreebsd(func, stk, stksz, flags, arg, tls, ptid, ctid); -#ifdef __x86_64__ - } else if (IsNetbsd()) { - rc = CloneNetbsd(func, stk, stksz, flags, arg, tls, ptid, ctid); - } else if (IsOpenbsd()) { - rc = CloneOpenbsd(func, stk, stksz, flags, arg, tls, ptid, ctid); + err = CloneFreebsd(func, stk, stksz, arg, tls, ptid, ctid); +#if defined(__x86_64__) } else if (IsWindows()) { - rc = CloneWindows(func, stk, stksz, flags, arg, tls, ptid, ctid); + err = CloneWindows(func, stk, stksz, arg, tls, ptid, ctid); + } else if (IsNetbsd()) { + err = CloneNetbsd(func, stk, stksz, arg, tls, ptid, ctid); + } else if (IsOpenbsd()) { + err = CloneOpenbsd(func, stk, stksz, arg, tls, ptid, ctid); #endif /* __x86_64__ */ } else { - rc = ENOSYS; + err = ENOSYS; } - if (SupportsBsd() && rc == EPROCLIM) { - rc = EAGAIN; - } + if (SupportsBsd() && err == EPROCLIM) + err = EAGAIN; - return rc; + if (err) + atomic_fetch_sub(&_pthread_count, 1); + + return err; } diff --git a/libc/runtime/cosmo.S b/libc/runtime/cosmo.S index 0b52e57d6..59fe944c1 100644 --- a/libc/runtime/cosmo.S +++ b/libc/runtime/cosmo.S @@ -32,8 +32,8 @@ // @param rdx is environ // @param rcx is auxv // @noreturn -cosmo: push %rbp - mov %rsp,%rbp +cosmo: beg + pro mov %edi,%r12d mov %rsi,%r13 mov %rdx,%r14 @@ -104,7 +104,10 @@ cosmo: push %rbp je 2f push %rax push %rax - call .Largs + mov %r12d,%edi + mov %r13,%rsi + mov %r14,%rdx + mov %r15,%rcx call *(%rax) pop %rax pop %rax @@ -112,17 +115,15 @@ cosmo: push %rbp jmp 1b // call main() -2: call .Largs +2: mov %r12d,%edi + mov %r13,%rsi + mov %r14,%rdx + mov %r15,%rcx .weak main call main xchg %eax,%edi call exit - -.Largs: mov %r12d,%edi - mov %r13,%rsi - mov %r14,%rdx - mov %r15,%rcx - ret + end .endfn cosmo,weak // Enables Thread Local Storage. diff --git a/libc/runtime/cosmo2.c b/libc/runtime/cosmo2.c index d2a80c66e..a218af579 100644 --- a/libc/runtime/cosmo2.c +++ b/libc/runtime/cosmo2.c @@ -93,7 +93,8 @@ wontreturn textstartup void cosmo(long *sp, struct Syslib *m1, char *exename, .tib_sigmask = -1, .tib_sigstack_size = 57344, .tib_sigstack_addr = (char *)__builtin_frame_address(0) - 57344, - .tib_tid = 1, + .tib_ptid = 1, + .tib_ctid = 1, }; __set_tls(&tib); diff --git a/libc/runtime/cxa_thread_atexit.c b/libc/runtime/cxa_thread_atexit.c index 76b89ec89..57ce06849 100644 --- a/libc/runtime/cxa_thread_atexit.c +++ b/libc/runtime/cxa_thread_atexit.c @@ -23,7 +23,6 @@ #include "libc/nexgen32e/gc.internal.h" #include "libc/thread/posixthread.internal.h" #include "libc/thread/tls.h" -#include "third_party/nsync/wait_s.internal.h" struct Dtor { void *fun; @@ -89,10 +88,7 @@ void __cxa_thread_finalize(void) { // thread has any thread-specific data, appropriate destructor // functions shall be called in an unspecified order." // ──Quoth POSIX.1-2017 - if (tib->tib_nsync) - _weaken(nsync_waiter_destroy)(tib->tib_nsync); _pthread_unkey(tib); - _pthread_ungarbage(tib); while ((dtor = tib->tib_atexit)) { diff --git a/libc/runtime/enable_tls.c b/libc/runtime/enable_tls.c index 3fc35201e..0296e6fda 100644 --- a/libc/runtime/enable_tls.c +++ b/libc/runtime/enable_tls.c @@ -220,7 +220,7 @@ textstartup void __enable_tls(void) { DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &hThread, 0, false, kNtDuplicateSameAccess); - atomic_store_explicit(&tib->tib_syshand, hThread, memory_order_relaxed); + atomic_init(&tib->tib_syshand, hThread); } else if (IsXnuSilicon()) { tib->tib_syshand = __syslib->__pthread_self(); } @@ -233,23 +233,22 @@ textstartup void __enable_tls(void) { } else { tid = sys_gettid(); } - atomic_store_explicit(&tib->tib_tid, tid, memory_order_relaxed); + atomic_init(&tib->tib_ptid, tid); + atomic_init(&tib->tib_ctid, tid); // TODO(jart): set_tid_address? // inherit signal mask - if (IsWindows()) { - atomic_store_explicit(&tib->tib_sigmask, - ParseMask(__getenv(environ, "_MASK").s), - memory_order_relaxed); - } + if (IsWindows()) + atomic_init(&tib->tib_sigmask, ParseMask(__getenv(environ, "_MASK").s)); // initialize posix threads _pthread_static.tib = tib; _pthread_static.pt_flags = PT_STATIC; _pthread_static.pt_locale = &__global_locale; + _pthread_static.pt_attr.__stackaddr = __maps.stack.addr; + _pthread_static.pt_attr.__stacksize = __maps.stack.size; dll_init(&_pthread_static.list); _pthread_list = &_pthread_static.list; - atomic_store_explicit(&_pthread_static.ptid, tid, memory_order_release); // ask the operating system to change the x86 segment register if (IsWindows()) diff --git a/libc/runtime/ftrace-hook.S b/libc/runtime/ftrace-hook.S index 3878c506d..cd25a18c4 100644 --- a/libc/runtime/ftrace-hook.S +++ b/libc/runtime/ftrace-hook.S @@ -22,38 +22,53 @@ ftrace_hook: #ifdef __x86_64__ -// We need to save saved registers because we have some functions -// like __errno_location which can be called from an inline asm() -// statement. It's nice to have the flexibility anyway. +// save argument registers +// we save %rax because __gc() takes it as an argument. +// we save %r10 because it's used as a syscall argument. cmpl $0,__ftrace(%rip) jle 1f + .cfi_startproc push %rbp + .cfi_def_cfa_offset 16 + .cfi_offset %rbp, -16 mov %rsp,%rbp + .cfi_def_cfa_register %rbp and $-16,%rsp - sub $256,%rsp + sub $128,%rsp + movdqu %xmm0,-0x80(%rbp) + movdqu %xmm1,-0x70(%rbp) + movdqu %xmm2,-0x60(%rbp) + movdqu %xmm3,-0x50(%rbp) + movdqu %xmm4,-0x40(%rbp) + movdqu %xmm5,-0x30(%rbp) + movdqu %xmm6,-0x20(%rbp) + movdqu %xmm7,-0x10(%rbp) push %rax - push %rbx + .cfi_offset %rax, -24 push %rcx + .cfi_offset %rcx, -32 push %rdx + .cfi_offset %rdx, -40 push %rdi + .cfi_offset %rdi, -48 push %rsi + .cfi_offset %rsi, -56 push %r8 + .cfi_offset %r8, -64 push %r9 + .cfi_offset %r9, -72 push %r10 - push %r11 - push %r12 - push %r13 - push %r14 - push %r15 - call __xmm_save + .cfi_offset %r10, -80 call ftracer - call __xmm_load - pop %r15 - pop %r14 - pop %r13 - pop %r12 - pop %r11 + movdqu -0x80(%rbp),%xmm0 + movdqu -0x70(%rbp),%xmm1 + movdqu -0x60(%rbp),%xmm2 + movdqu -0x50(%rbp),%xmm3 + movdqu -0x40(%rbp),%xmm4 + movdqu -0x30(%rbp),%xmm5 + movdqu -0x20(%rbp),%xmm6 + movdqu -0x10(%rbp),%xmm7 pop %r10 pop %r9 pop %r8 @@ -61,15 +76,22 @@ ftrace_hook: pop %rdi pop %rdx pop %rcx - pop %rbx pop %rax leave + .cfi_restore %rbp + .cfi_def_cfa %rsp, 8 1: ret + .cfi_endproc #elif defined(__aarch64__) stp x29,x30,[sp,-384]! + .cfi_startproc + .cfi_def_cfa_offset 384 + .cfi_offset 29, -384 // x29 (fp) is saved at [sp - 384] + .cfi_offset 30, -376 // x30 (lr) is saved at [sp - 376] mov x29,sp + .cfi_def_cfa_register 29 stp x0,x1,[sp,16] adrp x0,__ftrace @@ -78,18 +100,45 @@ ftrace_hook: ble 1f stp x2,x3,[sp,32] + .cfi_offset 2, -352 + .cfi_offset 3, -344 stp x4,x5,[sp,48] + .cfi_offset 4, -336 + .cfi_offset 5, -328 stp x6,x7,[sp,64] + .cfi_offset 6, -320 + .cfi_offset 7, -312 stp x8,x9,[sp,80] + .cfi_offset 8, -304 + .cfi_offset 9, -296 stp x10,x11,[sp,96] + .cfi_offset 10, -288 + .cfi_offset 11, -280 stp x12,x13,[sp,112] + .cfi_offset 12, -272 + .cfi_offset 13, -264 stp x14,x15,[sp,128] + .cfi_offset 14, -256 + .cfi_offset 15, -248 stp x16,x19,[sp,160] + .cfi_offset 16, -224 + .cfi_offset 19, -216 stp x20,x21,[sp,176] + .cfi_offset 20, -208 + .cfi_offset 21, -200 stp x22,x23,[sp,192] + .cfi_offset 22, -192 + .cfi_offset 23, -184 stp x24,x25,[sp,208] + .cfi_offset 24, -176 + .cfi_offset 25, -168 stp x26,x27,[sp,224] + .cfi_offset 26, -160 + .cfi_offset 27, -152 stp x17,x28,[sp,240] + .cfi_offset 17, -144 + .cfi_offset 28, -136 + // No CFI directives needed for FP registers stp q0,q1,[sp,256] stp q2,q3,[sp,288] stp q4,q5,[sp,320] @@ -117,7 +166,12 @@ ftrace_hook: 1: ldp x0,x1,[sp,16] ldp x29,x30,[sp],384 + .cfi_restore 29 + .cfi_restore 30 + .cfi_def_cfa 7, 0 // On some ARM systems the stack pointer is represented by register 7 + .cfi_def_cfa_offset 0 ret + .cfi_endproc #endif /* __x86_64__ */ .endfn ftrace_hook,globl,hidden diff --git a/libc/runtime/ftraceinit.greg.c b/libc/runtime/ftraceinit.greg.c index f0f4a1e48..0f18fcf68 100644 --- a/libc/runtime/ftraceinit.greg.c +++ b/libc/runtime/ftraceinit.greg.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/dce.h" #include "libc/runtime/internal.h" #include "libc/runtime/runtime.h" #include "libc/runtime/symbols.internal.h" @@ -37,7 +38,7 @@ __static_yoink("zipos"); * @see libc/runtime/_init.S for documentation */ textstartup int ftrace_init(void) { - if (strace_enabled(0) > 0) { + if (IsModeDbg() || strace_enabled(0) > 0) { GetSymbolTable(); } if (__intercept_flag(&__argc, __argv, "--ftrace")) { diff --git a/libc/runtime/ftracer.c b/libc/runtime/ftracer.c index ca09b1d5a..56f4d53f9 100644 --- a/libc/runtime/ftracer.c +++ b/libc/runtime/ftracer.c @@ -29,14 +29,9 @@ #include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" #include "libc/thread/tls.h" -#include "libc/thread/tls2.internal.h" /** - * @fileoverview Plain-text function call logging. - * - * Able to log ~2 million function calls per second, which is mostly - * bottlenecked by system call overhead. Log size is reasonable if piped - * into gzip. + * @fileoverview plain-text function call logging */ #define MAX_NESTING 512 @@ -50,7 +45,7 @@ static struct CosmoFtrace g_ftrace; __funline int GetNestingLevelImpl(struct StackFrame *frame) { - int nesting = -2; + int nesting = -1; while (frame && !kisdangerous(frame)) { ++nesting; frame = frame->next; @@ -83,38 +78,63 @@ privileged void ftracer(void) { struct StackFrame *sf; struct CosmoFtrace *ft; struct PosixThread *pt; + + // get interesting values sf = __builtin_frame_address(0); st = (uintptr_t)__argv - sizeof(uintptr_t); if (__ftrace <= 0) return; + + // determine top of stack + // main thread won't consider kernel provided argblock if (__tls_enabled) { tib = __get_tls_privileged(); if (tib->tib_ftrace <= 0) return; ft = &tib->tib_ftracer; - if ((char *)sf >= tib->tib_sigstack_addr && - (char *)sf <= tib->tib_sigstack_addr + tib->tib_sigstack_size) { - st = (uintptr_t)tib->tib_sigstack_addr + tib->tib_sigstack_size; - } else if ((pt = (struct PosixThread *)tib->tib_pthread) && - pt->pt_attr.__stacksize) { - st = (uintptr_t)pt->pt_attr.__stackaddr + pt->pt_attr.__stacksize; + pt = (struct PosixThread *)tib->tib_pthread; + if (pt != &_pthread_static) { + if ((char *)sf >= tib->tib_sigstack_addr && + (char *)sf <= tib->tib_sigstack_addr + tib->tib_sigstack_size) { + st = (uintptr_t)tib->tib_sigstack_addr + tib->tib_sigstack_size; + } else if (pt && pt->pt_attr.__stacksize) { + st = (uintptr_t)pt->pt_attr.__stackaddr + pt->pt_attr.__stacksize; + } } } else { ft = &g_ftrace; } - stackuse = st - (intptr_t)sf; - if (_cmpxchg(&ft->ft_once, false, true)) { + + // estimate stack pointer of hooked function + uintptr_t usp = (uintptr_t)sf; + usp += sizeof(struct StackFrame); // overhead of this function +#if defined(__x86_64__) + usp += 8; // ftrace_hook() stack aligning + usp += 8 * 8; // ftrace_hook() pushed 8x regs + usp += 8 * 16; // ftrace_hook() pushed 8x xmms +#elif defined(__aarch64__) + usp += 384; // overhead of ftrace_hook() +#else +#error "unsupported architecture" +#endif + + // determine how much stack hooked function is using + stackuse = st - usp; + + // log function call + // + // FUN $PID $TID $STARTNANOS $STACKUSE $SYMBOL + // + if (!ft->ft_once) { ft->ft_lastaddr = -1; ft->ft_skew = GetNestingLevelImpl(sf); + ft->ft_once = true; } - if (_cmpxchg(&ft->ft_noreentry, false, true)) { - sf = sf->next; - fn = sf->addr + DETOUR_SKEW; - if (fn != ft->ft_lastaddr) { - kprintf("%rFUN %6P %6H %'18T %'*ld %*s%t\n", ftrace_stackdigs, stackuse, - GetNestingLevel(ft, sf) * 2, "", fn); - ft->ft_lastaddr = fn; - } - ft->ft_noreentry = false; + sf = sf->next; + fn = sf->addr + DETOUR_SKEW; + if (fn != ft->ft_lastaddr) { + kprintf("%rFUN %6P %6H %'18T %'*ld %*s%t\n", ftrace_stackdigs, stackuse, + GetNestingLevel(ft, sf) * 2, "", fn); + ft->ft_lastaddr = fn; } } diff --git a/libc/runtime/hook.greg.c b/libc/runtime/hook.greg.c index d736eade2..795512481 100644 --- a/libc/runtime/hook.greg.c +++ b/libc/runtime/hook.greg.c @@ -119,6 +119,7 @@ privileged int __hook(void *dest, struct SymbolTable *st) { if (!st) return -1; __morph_begin(); + __jit_begin(); lowest = MAX((intptr_t)__executable_start, (intptr_t)_ereal); for (i = 0; i < st->count; ++i) { if (st->symbols[i].x < 9) @@ -138,6 +139,9 @@ privileged int __hook(void *dest, struct SymbolTable *st) { // kprintf("can't hook %t at %lx\n", p, p); } } + __clear_cache(MAX((char *)__executable_start, (char *)_ereal), + MIN((char *)__privileged_start, (char *)_etext)); + __jit_end(); __morph_end(); return 0; } diff --git a/libc/runtime/jit.c b/libc/runtime/jit.c index 6ea45ecb5..a418f75dc 100644 --- a/libc/runtime/jit.c +++ b/libc/runtime/jit.c @@ -20,7 +20,7 @@ #include "libc/runtime/runtime.h" #include "libc/runtime/syslib.internal.h" -void __jit_begin(void) { +privileged void __jit_begin(void) { if (IsXnuSilicon()) { if (__syslib->__pthread_jit_write_protect_supported_np()) { __syslib->__pthread_jit_write_protect_np(false); @@ -28,7 +28,7 @@ void __jit_begin(void) { } } -void __jit_end(void) { +privileged void __jit_end(void) { if (IsXnuSilicon()) { if (__syslib->__pthread_jit_write_protect_supported_np()) { __syslib->__pthread_jit_write_protect_np(true); diff --git a/libc/runtime/morph.c b/libc/runtime/morph.c index 08abcc410..c3bcc4ae3 100644 --- a/libc/runtime/morph.c +++ b/libc/runtime/morph.c @@ -24,12 +24,13 @@ #include "libc/intrin/kprintf.h" #include "libc/nt/enum/pageflags.h" #include "libc/nt/memory.h" +#include "libc/nt/runtime.h" #include "libc/nt/thunk/msabi.h" #include "libc/runtime/runtime.h" #include "libc/sysv/consts/nr.h" #include "libc/sysv/consts/prot.h" -__msabi extern typeof(VirtualProtect) *const __imp_VirtualProtect; +__msabi extern typeof(VirtualProtectEx) *const __imp_VirtualProtectEx; __funline void __morph_mprotect(void *addr, size_t size, int prot, int ntprot) { #ifdef __x86_64__ @@ -54,7 +55,7 @@ __funline void __morph_mprotect(void *addr, size_t size, int prot, int ntprot) { } #endif } else { - __imp_VirtualProtect(addr, size, ntprot, &op); + __imp_VirtualProtectEx(GetCurrentProcess(), addr, size, ntprot, &op); } #elif defined(__aarch64__) register long r0 asm("x0") = (long)addr; diff --git a/libc/runtime/opensymboltable.greg.c b/libc/runtime/opensymboltable.greg.c index 5da44f023..145c8be21 100644 --- a/libc/runtime/opensymboltable.greg.c +++ b/libc/runtime/opensymboltable.greg.c @@ -130,7 +130,8 @@ static struct SymbolTable *OpenSymbolTableImpl(const char *filename) { ++j; } t->count = j; - munmap(stp, sizeof(const Elf64_Sym *) * n); + if (!IsWindows()) + munmap(stp, sizeof(const Elf64_Sym *) * n); munmap(map, filesize); close(fd); return t; @@ -144,9 +145,8 @@ RaiseEnoexec: errno = ENOEXEC; SystemError: STRACE("OpenSymbolTable()% m"); - if (map != MAP_FAILED) { + if (map != MAP_FAILED) munmap(map, filesize); - } close(fd); return 0; } diff --git a/libc/runtime/runtime.h b/libc/runtime/runtime.h index 452125bcb..4ea96a3cc 100644 --- a/libc/runtime/runtime.h +++ b/libc/runtime/runtime.h @@ -83,7 +83,6 @@ extern uint64_t kStartTsc; extern const char kNtSystemDirectory[]; extern const char kNtWindowsDirectory[]; extern size_t __virtualmax; -extern size_t __virtualsize; extern size_t __stackmax; extern bool32 __isworker; /* utilities */ @@ -95,6 +94,7 @@ int ftrace_install(void) libcesque; int ftrace_enabled(int) libcesque; int strace_enabled(int) libcesque; void __print_maps(size_t) libcesque; +void __print_maps_win32(int64_t, const char *, size_t) libcesque; void __printargs(const char *) libcesque; /* builtin sh-like system/popen dsl */ int _cocmd(int, char **, char **) libcesque; diff --git a/libc/runtime/set_tls.c b/libc/runtime/set_tls.c index 0ed3609d0..c8385bacc 100644 --- a/libc/runtime/set_tls.c +++ b/libc/runtime/set_tls.c @@ -24,7 +24,6 @@ #include "libc/nt/thread.h" #include "libc/sysv/consts/arch.h" #include "libc/thread/tls.h" -#include "libc/thread/tls2.internal.h" #define AMD64_SET_FSBASE 129 #define AMD64_SET_GSBASE 131 diff --git a/libc/runtime/sigsetjmp.S b/libc/runtime/sigsetjmp.S index 1ef838cce..3187bd295 100644 --- a/libc/runtime/sigsetjmp.S +++ b/libc/runtime/sigsetjmp.S @@ -29,7 +29,7 @@ // Saves caller CPU state and signal mask. // -// @param rdi points to jmp_buf +// @param rdi points to sigjmp_buf // @param esi if non-zero will cause mask to be saved // @return eax 0 when set and !0 when longjmp'd // @returnstwice diff --git a/libc/runtime/stack.h b/libc/runtime/stack.h index 87dcd2440..d526bb3da 100644 --- a/libc/runtime/stack.h +++ b/libc/runtime/stack.h @@ -69,9 +69,8 @@ uintptr_t GetStackBottom(void) pureconst; * will also trigger the stack to grow down safely. */ forceinline void CheckLargeStackAllocation(void *p, ssize_t n) { - for (; n > 0; n -= 4096) { - ((char *)p)[n - 1] = 0; - } + for (; n > 0; n -= 4096) + ((volatile char *)p)[n - 1] = 0; } void *NewCosmoStack(void) vallocesque; diff --git a/libc/runtime/sysconf.c b/libc/runtime/sysconf.c index 17ff15c54..4f3dafd75 100644 --- a/libc/runtime/sysconf.c +++ b/libc/runtime/sysconf.c @@ -45,8 +45,8 @@ * - `_SC_GRANSIZE` returns addr alignment for mmap() * - `_SC_CLK_TCK` returns number of clock ticks per second * - `_SC_ARG_MAX` will perform expensive rlimit calculations - * - `_SC_SIGSTKSZ` returns host platform's preferred SIGSTKSZ - * - `_SC_MINSIGSTKSZ` returns host platform's required MINSIGSTKSZ + * - `_SC_SIGSTKSZ` returns recommended `SIGSTKSZ` for platform + * - `_SC_MINSIGSTKSZ` returns size of kernel pushed signal frame * - `_SC_AVPHYS_PAGES` returns average physical memory pages * - `_SC_PHYS_PAGES` returns physical memory pages available * - `_SC_NPROCESSORS_ONLN` returns number of effective CPUs @@ -67,7 +67,7 @@ long sysconf(int name) { case _SC_ARG_MAX: return __get_arg_max(); case _SC_SIGSTKSZ: - return _SIGSTKSZ; + return __get_minsigstksz() + SIGSTKSZ; case _SC_MINSIGSTKSZ: return __get_minsigstksz(); case _SC_CHILD_MAX: diff --git a/libc/runtime/winmain.greg.c b/libc/runtime/winmain.greg.c index 5bf331879..640314f93 100644 --- a/libc/runtime/winmain.greg.c +++ b/libc/runtime/winmain.greg.c @@ -17,11 +17,11 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/atomic.h" #include "libc/calls/internal.h" #include "libc/calls/sig.internal.h" #include "libc/calls/syscall_support-nt.internal.h" #include "libc/intrin/dll.h" -#include "libc/intrin/kprintf.h" #include "libc/intrin/maps.h" #include "libc/intrin/nomultics.h" #include "libc/intrin/weaken.h" @@ -31,6 +31,7 @@ #include "libc/nt/accounting.h" #include "libc/nt/console.h" #include "libc/nt/enum/consolemodeflags.h" +#include "libc/nt/enum/creationdisposition.h" #include "libc/nt/enum/filemapflags.h" #include "libc/nt/enum/pageflags.h" #include "libc/nt/files.h" @@ -51,6 +52,7 @@ #include "libc/sock/internal.h" #include "libc/str/str.h" #include "libc/sysv/consts/prot.h" +#include "libc/thread/tls.h" #ifdef __x86_64__ #define abi __msabi textwindows dontinstrument @@ -77,7 +79,7 @@ __msabi extern typeof(SetConsoleMode) *const __imp_SetConsoleMode; __msabi extern typeof(SetConsoleOutputCP) *const __imp_SetConsoleOutputCP; __msabi extern typeof(SetEnvironmentVariable) *const __imp_SetEnvironmentVariableW; __msabi extern typeof(SetStdHandle) *const __imp_SetStdHandle; -__msabi extern typeof(VirtualProtect) *const __imp_VirtualProtect; +__msabi extern typeof(VirtualProtectEx) *const __imp_VirtualProtectEx; __msabi extern typeof(WriteFile) *const __imp_WriteFile; // clang-format on @@ -86,11 +88,15 @@ void __stack_call(int, char **, char **, long (*)[2], void (*)(int, char **, char **, long (*)[2]), intptr_t) wontreturn; +bool __winmain_isfork; +intptr_t __winmain_jmpbuf[5]; +struct CosmoTib *__winmain_tib; + __funline int IsAlpha(int c) { return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'); } -static abi char16_t *StrStr(const char16_t *haystack, const char16_t *needle) { +abi static char16_t *StrStr(const char16_t *haystack, const char16_t *needle) { size_t i; for (;;) { for (i = 0;; ++i) { @@ -107,13 +113,13 @@ static abi char16_t *StrStr(const char16_t *haystack, const char16_t *needle) { return 0; } -static abi void PrintError(const char *s, size_t n) { +abi static void PrintError(const char *s, size_t n) { #define PrintError(s) PrintError(s, sizeof(s) - 1) __imp_WriteFile(__imp_GetStdHandle(kNtStdErrorHandle), s, n, 0, 0); } // detect the unholiest of environments -static abi bool32 IsWslChimera(void) { +abi static bool32 IsWslChimera(void) { char16_t path[PATH_MAX]; return __imp_GetCurrentDirectoryW(PATH_MAX, path) && // path[0] == '\\' && // @@ -124,7 +130,7 @@ static abi bool32 IsWslChimera(void) { } // returns true if utf-8 path is a win32-style path that exists -static abi bool32 WinFileExists(const char *path) { +abi static bool32 WinFileExists(const char *path) { uint16_t path16[PATH_MAX]; size_t z = ARRAYLEN(path16); size_t n = tprecode8to16(path16, z, path).ax; @@ -134,7 +140,7 @@ static abi bool32 WinFileExists(const char *path) { } // this ensures close(1) won't accidentally close(2) for example -static abi void DeduplicateStdioHandles(void) { +abi static void DeduplicateStdioHandles(void) { for (long i = 0; i < 3; ++i) { int64_t h1 = __imp_GetStdHandle(kNtStdio[i]); for (long j = i + 1; j < 3; ++j) { @@ -149,19 +155,19 @@ static abi void DeduplicateStdioHandles(void) { } } -static bool32 HasEnvironmentVariable(const char16_t *name) { +abi static bool32 HasEnvironmentVariable(const char16_t *name) { char16_t buf[4]; return __imp_GetEnvironmentVariableW(name, buf, ARRAYLEN(buf)); } -static abi unsigned OnWinCrash(struct NtExceptionPointers *ep) { +abi static unsigned OnWinCrash(struct NtExceptionPointers *ep) { int code, sig = __sig_crash_sig(ep->ExceptionRecord->ExceptionCode, &code); TerminateThisProcess(sig); } // main function of windows init process // i.e. first process spawned that isn't forked -static abi wontreturn void WinInit(const char16_t *cmdline) { +abi wontreturn static void WinInit(const char16_t *cmdline) { __oldstack = (intptr_t)__builtin_frame_address(0); __imp_SetConsoleOutputCP(kNtCpUtf8); @@ -200,11 +206,12 @@ static abi wontreturn void WinInit(const char16_t *cmdline) { int stackprot = (intptr_t)ape_stack_prot; if (~stackprot & PROT_EXEC) { uint32_t old; - __imp_VirtualProtect(stackaddr, stacksize, kNtPageReadwrite, &old); + __imp_VirtualProtectEx(GetCurrentProcess(), stackaddr, stacksize, + kNtPageReadwrite, &old); } uint32_t oldattr; - __imp_VirtualProtect(stackaddr, GetGuardSize(), - kNtPageReadwrite | kNtPageGuard, &oldattr); + __imp_VirtualProtectEx(GetCurrentProcess(), stackaddr, GetGuardSize(), + kNtPageReadwrite | kNtPageGuard, &oldattr); if (_weaken(__maps_stack)) { struct NtSystemInfo si; __imp_GetSystemInfo(&si); @@ -294,18 +301,51 @@ static abi wontreturn void WinInit(const char16_t *cmdline) { ARRAYLEN(wa->envp) - 1); __imp_FreeEnvironmentStringsW(env16); __envp = &wa->envp[0]; - // handover control to cosmopolitan runtime __stack_call(count, wa->argv, wa->envp, wa->auxv, cosmo, (uintptr_t)(stackaddr + (stacksize - sizeof(struct WinArgs)))); } +static int Atoi(const char16_t *str) { + int c; + unsigned x = 0; + while ((c = *str++)) { + if ('0' <= c && c <= '9') { + x *= 10; + x += c - '0'; + } else { + return -1; + } + } + return x; +} + +abi static int WinGetPid(const char16_t *var, bool *out_is_inherited) { + uint32_t len; + char16_t val[12]; + if ((len = __imp_GetEnvironmentVariableW(var, val, ARRAYLEN(val)))) { + int pid = -1; + if (len < ARRAYLEN(val)) + pid = Atoi(val); + __imp_SetEnvironmentVariableW(var, NULL); + if (pid > 0) { + *out_is_inherited = true; + return pid; + } + } + *out_is_inherited = false; + return __imp_GetCurrentProcessId(); +} + abi int64_t WinMain(int64_t hInstance, int64_t hPrevInstance, const char *lpCmdLine, int64_t nCmdShow) { + static atomic_ulong fake_process_signals; const char16_t *cmdline; extern char os asm("__hostos"); os = _HOSTWINDOWS; // madness https://news.ycombinator.com/item?id=21019722 kStartTsc = rdtsc(); + __tls_enabled = false; + ftrace_enabled(-1); if (!IsTiny() && IsWslChimera()) { PrintError("error: APE is running on WIN32 inside WSL. You need to run: " "sudo sh -c 'echo -1 > /proc/sys/fs/binfmt_misc/WSLInterop'\n"); @@ -315,21 +355,24 @@ abi int64_t WinMain(int64_t hInstance, int64_t hPrevInstance, __imp_GetSystemInfo(&si); __pagesize = si.dwPageSize; __gransize = si.dwAllocationGranularity; - __umask = 077; - __pid = __imp_GetCurrentProcessId(); + bool pid_is_inherited; + __pid = WinGetPid(u"_COSMO_PID", &pid_is_inherited); + if (!(__sig.process = __sig_map_process(__pid, kNtOpenAlways))) + __sig.process = &fake_process_signals; + if (__winmain_isfork) + __builtin_longjmp(__winmain_jmpbuf, 1); + if (!pid_is_inherited) + atomic_store_explicit(__sig.process, 0, memory_order_release); cmdline = __imp_GetCommandLineW(); #if SYSDEBUG // sloppy flag-only check for early initialization if (StrStr(cmdline, u"--strace")) ++__strace; #endif - if (_weaken(WinSockInit)) { + ftrace_enabled(+1); + if (_weaken(WinSockInit)) _weaken(WinSockInit)(); - } DeduplicateStdioHandles(); - if (_weaken(WinMainForked)) { - _weaken(WinMainForked)(); - } WinInit(cmdline); } diff --git a/libc/runtime/zipos-find.c b/libc/runtime/zipos-find.c index 15443064e..7431c5e9a 100644 --- a/libc/runtime/zipos-find.c +++ b/libc/runtime/zipos-find.c @@ -16,7 +16,6 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/kprintf.h" #include "libc/macros.h" #include "libc/runtime/zipos.internal.h" #include "libc/str/str.h" @@ -44,9 +43,8 @@ ssize_t __zipos_scan(struct Zipos *zipos, struct ZiposUri *name) { // strip trailing slash from search name int len = name->len; - if (len && name->path[len - 1] == '/') { + if (len && name->path[len - 1] == '/') --len; - } // empty string means the /zip root directory if (!len) { @@ -91,9 +89,8 @@ ssize_t __zipos_scan(struct Zipos *zipos, struct ZiposUri *name) { dx = dx < -1 ? -1 : dx; for (l += dx; 0 <= l && l < zipos->records; l += dx) { ssize_t cf; - if ((cf = __zipos_match(zipos, name, len, l)) != -1) { + if ((cf = __zipos_match(zipos, name, len, l)) != -1) return cf; - } cfile = zipos->index[l]; zname = ZIP_CFILE_NAME(zipos->map + cfile); zsize = ZIP_CFILE_NAMESIZE(zipos->map + cfile); diff --git a/libc/runtime/zipos-get.c b/libc/runtime/zipos-get.c index 55660e285..e98b6b363 100644 --- a/libc/runtime/zipos-get.c +++ b/libc/runtime/zipos-get.c @@ -63,11 +63,9 @@ static void __zipos_dismiss(uint8_t *map, const uint8_t *cdir, long pg) { } // unmap the executable portion beneath the local files - if (!IsWindows()) { - mo = ROUNDDOWN(lo, __gransize); - if (mo) - munmap(map, mo); - } + mo = ROUNDDOWN(lo, __gransize); + if (mo && !IsWindows()) + munmap(map, mo); } static int __zipos_compare_names(const void *a, const void *b, void *c) { diff --git a/libc/sock/getsockopt-nt.c b/libc/sock/getsockopt-nt.c index 680bf6fc2..631cd25e2 100644 --- a/libc/sock/getsockopt-nt.c +++ b/libc/sock/getsockopt-nt.c @@ -39,7 +39,6 @@ textwindows int sys_getsockopt_nt(struct Fd *fd, int level, int optname, uint64_t ms; uint32_t in_optlen; struct linger_nt linger; - npassert(fd->kind == kFdSocket); if (out_opt_optval && inout_optlen) { in_optlen = *inout_optlen; diff --git a/libc/sock/ifaddrs.c b/libc/sock/ifaddrs.c index 01c0d8e05..9ea609e09 100644 --- a/libc/sock/ifaddrs.c +++ b/libc/sock/ifaddrs.c @@ -18,13 +18,19 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/sock/ifaddrs.h" #include "libc/calls/calls.h" +#include "libc/calls/syscall-sysv.internal.h" +#include "libc/dce.h" +#include "libc/limits.h" #include "libc/mem/mem.h" #include "libc/sock/sock.h" #include "libc/sock/struct/ifconf.h" #include "libc/sock/struct/ifreq.h" +#include "libc/sock/struct/sockaddr6.h" +#include "libc/stdio/stdio.h" #include "libc/str/str.h" #include "libc/sysv/consts/af.h" #include "libc/sysv/consts/iff.h" +#include "libc/sysv/consts/o.h" #include "libc/sysv/consts/sio.h" #include "libc/sysv/consts/sock.h" @@ -36,6 +42,20 @@ struct IfAddr { struct sockaddr_in bstaddr; }; +struct IfAddr6Info { + int addr_scope; + int addr_flags; +}; + +struct IfAddr6 { + struct ifaddrs ifaddrs; + char name[IFNAMSIZ]; + struct sockaddr_in6 addr; + struct sockaddr_in6 netmask; + struct sockaddr_in6 bstaddr; // unused + struct IfAddr6Info info; +}; + /** * Frees network interface address list. */ @@ -48,6 +68,73 @@ void freeifaddrs(struct ifaddrs *ifp) { } } +// hex repr to network order int +static uint128_t hex2no(const char *str) { + uint128_t res = 0; + const int max_quads = sizeof(uint128_t) * 2; + int i = 0; + while ((i < max_quads) && str[i]) { + uint8_t acc = (((str[i] & 0xF) + (str[i] >> 6)) | ((str[i] >> 3) & 0x8)); + acc = acc << 4; + i += 1; + if (str[i]) { + acc = acc | (((str[i] & 0xF) + (str[i] >> 6)) | ((str[i] >> 3) & 0x8)); + i += 1; + } + res = (res >> 8) | (((uint128_t)acc) << ((sizeof(uint128_t) - 1) * 8)); + } + res = res >> ((max_quads - i) * 4); + return res; +} + +/** + * Gets network interface IPv6 address list on linux. + * + * @return 0 on success, or -1 w/ errno + */ +static int getifaddrs_linux_ip6(struct ifconf *conf) { + int fd; + int n = 0; + struct ifreq *ifreq = conf->ifc_req; + const int bufsz = 44 + IFNAMSIZ + 1; + char buf[bufsz + 1]; // one line max size + if ((fd = sys_openat(0, "/proc/net/if_inet6", O_RDONLY, 0)) == -1) { + return -1; + } + + while ((n = sys_read(fd, &buf[n], bufsz - n)) && + ((char *)ifreq < (conf->ifc_buf + conf->ifc_len))) { + // flags linux include/uapi/linux/if_addr.h:44 + // scope linux include/net/ipv6.h:L99 + + // *addr, *index, *plen, *scope, *flags, *ifname + char *s[] = {&buf[0], &buf[33], &buf[36], &buf[39], &buf[42], &buf[45]}; + int ifnamelen = 0; + while (*s[5] == ' ') { + ++s[5]; + } + while (s[5][ifnamelen] > '\n') { + ++ifnamelen; + } + buf[32] = buf[35] = buf[38] = buf[41] = buf[44] = s[5][ifnamelen] = '\0'; + bzero(ifreq, sizeof(*ifreq)); + ifreq->ifr_addr.sa_family = AF_INET6; + memcpy(&ifreq->ifr_name, s[5], ifnamelen); + *((uint128_t *)&ifreq->ifr6_addr) = hex2no(s[0]); + ifreq->ifr6_ifindex = hex2no(s[1]); + ifreq->ifr6_prefixlen = hex2no(s[2]); + ifreq->ifr6_scope = hex2no(s[3]); + ifreq->ifr6_flags = hex2no(s[4]); + ++ifreq; + int tlen = &s[5][ifnamelen] - &buf[0] + 1; + n = bufsz - tlen; + memcpy(&buf, &buf[tlen], n); + } + + conf->ifc_len = (char *)ifreq - conf->ifc_buf; + return sys_close(fd); +} + /** * Gets network interface address list. * @@ -55,6 +142,7 @@ void freeifaddrs(struct ifaddrs *ifp) { * @see tool/viz/getifaddrs.c for example code */ int getifaddrs(struct ifaddrs **out_ifpp) { + // printf("%d\n", sizeof(struct ifreq)); int rc = -1; int fd; if ((fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0)) != -1) { @@ -65,42 +153,88 @@ int getifaddrs(struct ifaddrs **out_ifpp) { conf.ifc_buf = data; conf.ifc_len = size; if (!ioctl(fd, SIOCGIFCONF, &conf)) { + if (IsLinux()) { + struct ifconf confl6; + confl6.ifc_buf = data + conf.ifc_len; + confl6.ifc_len = size - conf.ifc_len; + if ((rc = getifaddrs_linux_ip6(&confl6))) + return rc; + conf.ifc_len += confl6.ifc_len; + } + struct ifaddrs *res = 0; for (struct ifreq *ifr = (struct ifreq *)data; (char *)ifr < data + conf.ifc_len; ++ifr) { - if (ifr->ifr_addr.sa_family != AF_INET) { - continue; // TODO(jart): IPv6 support - } - struct IfAddr *addr; - if ((addr = calloc(1, sizeof(struct IfAddr)))) { - memcpy(addr->name, ifr->ifr_name, IFNAMSIZ); - addr->ifaddrs.ifa_name = addr->name; - memcpy(&addr->addr, &ifr->ifr_addr, sizeof(struct sockaddr_in)); - addr->ifaddrs.ifa_addr = (struct sockaddr *)&addr->addr; - addr->ifaddrs.ifa_netmask = (struct sockaddr *)&addr->netmask; - if (!ioctl(fd, SIOCGIFFLAGS, ifr)) { - addr->ifaddrs.ifa_flags = ifr->ifr_flags; + uint16_t family = ifr->ifr_addr.sa_family; + if (family == AF_INET) { + struct IfAddr *addr; + if ((addr = calloc(1, sizeof(struct IfAddr)))) { + memcpy(addr->name, ifr->ifr_name, IFNAMSIZ); + addr->ifaddrs.ifa_name = addr->name; + memcpy(&addr->addr, &ifr->ifr_addr, sizeof(struct sockaddr_in)); + addr->ifaddrs.ifa_addr = (struct sockaddr *)&addr->addr; + addr->ifaddrs.ifa_netmask = (struct sockaddr *)&addr->netmask; + if (!ioctl(fd, SIOCGIFFLAGS, ifr)) { + addr->ifaddrs.ifa_flags = ifr->ifr_flags; + } + if (!ioctl(fd, SIOCGIFNETMASK, ifr)) { + memcpy(&addr->netmask, &ifr->ifr_addr, + sizeof(struct sockaddr_in)); + } + unsigned long op; + if (addr->ifaddrs.ifa_flags & IFF_BROADCAST) { + op = SIOCGIFBRDADDR; + } else if (addr->ifaddrs.ifa_flags & IFF_POINTOPOINT) { + op = SIOCGIFDSTADDR; + } else { + op = 0; + } + if (op && !ioctl(fd, op, ifr)) { + memcpy(&addr->bstaddr, &ifr->ifr_addr, + sizeof(struct sockaddr_in)); + addr->ifaddrs.ifa_broadaddr = // is union'd w/ ifu_dstaddr + (struct sockaddr *)&addr->bstaddr; + } + addr->ifaddrs.ifa_next = res; + res = (struct ifaddrs *)addr; } - if (!ioctl(fd, SIOCGIFNETMASK, ifr)) { - memcpy(&addr->netmask, &ifr->ifr_addr, - sizeof(struct sockaddr_in)); + } else if (family == AF_INET6) { + struct IfAddr6 *addr6; + if ((addr6 = calloc(1, sizeof(struct IfAddr6)))) { + addr6->ifaddrs.ifa_name = addr6->name; + addr6->ifaddrs.ifa_addr = (struct sockaddr *)&addr6->addr; + addr6->ifaddrs.ifa_netmask = (struct sockaddr *)&addr6->netmask; + addr6->ifaddrs.ifa_broadaddr = (struct sockaddr *)&addr6->bstaddr; + addr6->ifaddrs.ifa_data = (void *)&addr6->info; + + memcpy(&addr6->name, &ifr->ifr_name, IFNAMSIZ); + addr6->info.addr_flags = ifr->ifr6_flags; + addr6->info.addr_scope = ifr->ifr6_scope; + + addr6->addr.sin6_family = AF_INET6; + addr6->addr.sin6_port = 0; + addr6->addr.sin6_flowinfo = 0; + addr6->addr.sin6_scope_id = ifr->ifr6_ifindex; + memcpy(&addr6->addr.sin6_addr, &ifr->ifr6_addr, + sizeof(struct in6_addr)); + + addr6->netmask.sin6_family = AF_INET6; + addr6->netmask.sin6_port = 0; + addr6->netmask.sin6_flowinfo = 0; + addr6->addr.sin6_scope_id = ifr->ifr6_ifindex; + memcpy(&addr6->netmask.sin6_addr, &ifr->ifr6_addr, + sizeof(struct in6_addr)); + *((uint128_t *)&(addr6->netmask.sin6_addr)) &= + (UINT128_MAX >> ifr->ifr6_prefixlen); + + if (!ioctl(fd, SIOCGIFFLAGS, ifr)) { + addr6->ifaddrs.ifa_flags = ifr->ifr_flags; + } + + bzero(&addr6->bstaddr, sizeof(struct sockaddr_in6)); + addr6->ifaddrs.ifa_next = res; + res = (struct ifaddrs *)addr6; } - unsigned long op; - if (addr->ifaddrs.ifa_flags & IFF_BROADCAST) { - op = SIOCGIFBRDADDR; - } else if (addr->ifaddrs.ifa_flags & IFF_POINTOPOINT) { - op = SIOCGIFDSTADDR; - } else { - op = 0; - } - if (op && !ioctl(fd, op, ifr)) { - memcpy(&addr->bstaddr, &ifr->ifr_addr, - sizeof(struct sockaddr_in)); - addr->ifaddrs.ifa_broadaddr = // is union'd w/ ifu_dstaddr - (struct sockaddr *)&addr->bstaddr; - } - addr->ifaddrs.ifa_next = res; - res = (struct ifaddrs *)addr; } } *out_ifpp = res; diff --git a/libc/sock/internal.h b/libc/sock/internal.h index b8704360b..927b531fe 100644 --- a/libc/sock/internal.h +++ b/libc/sock/internal.h @@ -2,6 +2,7 @@ #define COSMOPOLITAN_LIBC_SOCK_INTERNAL_H_ #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/sigset.h" +#include "libc/calls/struct/timespec.h" #include "libc/nt/struct/overlapped.h" #include "libc/nt/thunk/msabi.h" #include "libc/nt/winsock.h" @@ -60,12 +61,12 @@ int sys_socketpair_nt_stream(int, int, int, int[2]) ; int sys_socketpair_nt_dgram(int, int, int, int[2]) ; */ int sys_socketpair_nt(int, int, int, int[2]); -int sys_select_nt(int, fd_set *, fd_set *, fd_set *, struct timeval *, +int sys_select_nt(int, fd_set *, fd_set *, fd_set *, const struct timespec *, const sigset_t *); size_t __iovec2nt(struct NtIovec[hasatleast 16], const struct iovec *, size_t); -ssize_t __winsock_block(int64_t, uint32_t, int, uint32_t, uint64_t, +ssize_t __winsock_block(int64_t, uint32_t, bool, uint32_t, uint64_t, int (*)(int64_t, struct NtOverlapped *, uint32_t *, void *), void *); diff --git a/libc/sock/kntwsadata.c b/libc/sock/kntwsadata.c index 2c08015e1..6e03dc588 100644 --- a/libc/sock/kntwsadata.c +++ b/libc/sock/kntwsadata.c @@ -51,3 +51,7 @@ textwindows void WinSockInit(void) { _Exit(1); } } + +textwindows dontinstrument void WinSockFork(void) { + WSAStartup(VERSION, &kNtWsaData); +} diff --git a/libc/sock/recv-nt.c b/libc/sock/recv-nt.c index 14b0e0edd..013fc930a 100644 --- a/libc/sock/recv-nt.c +++ b/libc/sock/recv-nt.c @@ -17,15 +17,17 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/internal.h" -#include "libc/calls/struct/iovec.h" #include "libc/calls/struct/sigset.internal.h" -#include "libc/intrin/fds.h" #include "libc/nt/struct/iovec.h" +#include "libc/nt/struct/overlapped.h" +#include "libc/nt/thunk/msabi.h" #include "libc/nt/winsock.h" #include "libc/sock/internal.h" #include "libc/sock/syscall_fd.internal.h" +#include "libc/sysv/consts/fio.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/errfuns.h" +#include "libc/vga/vga.internal.h" #ifdef __x86_64__ #define _MSG_OOB 1 @@ -33,6 +35,8 @@ #define _MSG_WAITALL 8 #define _MSG_DONTWAIT 64 +__msabi extern typeof(__sys_ioctlsocket_nt) *const __imp_ioctlsocket; + struct RecvArgs { const struct iovec *iov; size_t iovlen; @@ -54,13 +58,24 @@ textwindows ssize_t sys_recv_nt(int fd, const struct iovec *iov, size_t iovlen, return einval(); ssize_t rc; struct Fd *f = g_fds.p + fd; - sigset_t m = __sig_block(); - bool nonblock = !(flags & _MSG_WAITALL) && - ((f->flags & O_NONBLOCK) || (flags & _MSG_DONTWAIT)); - flags &= ~_MSG_DONTWAIT; - rc = __winsock_block(f->handle, flags, nonblock, f->rcvtimeo, m, - sys_recv_nt_start, &(struct RecvArgs){iov, iovlen}); - __sig_unblock(m); + sigset_t waitmask = __sig_block(); + + // "Be aware that if the underlying transport provider does not + // support MSG_WAITALL, or if the socket is in a non-blocking mode, + // then this call will fail with WSAEOPNOTSUPP. Also, if MSG_WAITALL + // is specified along with MSG_OOB, MSG_PEEK, or MSG_PARTIAL, then + // this call will fail with WSAEOPNOTSUPP." + // —Quoth MSDN § WSARecv + if (flags & _MSG_WAITALL) + __imp_ioctlsocket(f->handle, FIONBIO, (uint32_t[]){0}); + + rc = __winsock_block(f->handle, flags & ~_MSG_DONTWAIT, + (f->flags & O_NONBLOCK) || (flags & _MSG_DONTWAIT), + f->rcvtimeo, waitmask, sys_recv_nt_start, + &(struct RecvArgs){iov, iovlen}); + + __sig_unblock(waitmask); + return rc; } diff --git a/libc/sock/recv.c b/libc/sock/recv.c index 8d97329b3..31003e7fb 100644 --- a/libc/sock/recv.c +++ b/libc/sock/recv.c @@ -20,20 +20,40 @@ #include "libc/calls/internal.h" #include "libc/calls/struct/iovec.internal.h" #include "libc/dce.h" +#include "libc/intrin/describeflags.h" #include "libc/intrin/strace.h" #include "libc/sock/internal.h" #include "libc/sock/sock.h" #include "libc/sock/syscall_fd.internal.h" +#include "libc/sysv/consts/msg.h" #include "libc/sysv/errfuns.h" /** * Receives data from network socket. * + * Calling `recv(fd, p, n, 0)` is equivalent to `read(fd, p, n)`. + * + * Unlike files where the OS tries very hard to fulfill the entire + * requested `size` before returning, read operations on sockets aim to + * return as quickly as possible. For example, if 10 bytes are requested + * and a packet comes in with only 5 bytes, then recv() will most likely + * return those 5 bytes before waiting longer. The `MSG_WAITALL` flag + * may be passed when waiting longer is desired. In that case, short + * reads should only be possible when the connection status changes or + * the receive operation is interrupted by a signal. + * * @param fd is the file descriptor returned by socket() * @param buf is where received network data gets copied * @param size is the byte capacity of buf * @param flags can have `MSG_OOB`, `MSG_PEEK`, `MSG_DONTWAIT`, `MSG_WAITALL` * @return number of bytes received, 0 on remote close, or -1 w/ errno + * @raise EINTR if signal handler was called instead + * @raise EINVAL if unknown bits were passed in `flags` + * @raise EINVAL if flag isn't supported by host operating system + * @raise EINVAL if `MSG_WAITALL` and `MSG_PEEK` were both passed + * @raise EBADF if `fd` is an invalid file descriptor + * @raise EAGAIN if `MSG_DONTWAIT` was passed and no data was available + * @raise EAGAIN if `O_NONBLOCK` is in play and no data was available * @error EINTR, EHOSTUNREACH, ECONNRESET (UDP ICMP Port Unreachable), * EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc. * @cancelationpoint @@ -44,7 +64,11 @@ ssize_t recv(int fd, void *buf, size_t size, int flags) { ssize_t rc; BEGIN_CANCELATION_POINT; - if (fd < g_fds.n && g_fds.p[fd].kind == kFdZip) { + if ((flags & (MSG_WAITALL | MSG_PEEK)) == (MSG_WAITALL | MSG_PEEK)) { + // this is possible on some OSes like Linux but it breaks FreeBSD + // and Windows will raise EOPNOTSUPP when it gets passed together + return einval(); + } else if (fd < g_fds.n && g_fds.p[fd].kind == kFdZip) { rc = enotsock(); } else if (!IsWindows()) { rc = sys_recvfrom(fd, buf, size, flags, 0, 0); @@ -65,7 +89,8 @@ ssize_t recv(int fd, void *buf, size_t size, int flags) { } END_CANCELATION_POINT; - DATATRACE("recv(%d, [%#.*hhs%s], %'zu, %#x) → %'ld% lm", fd, - MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, flags, rc); + DATATRACE("recv(%d, [%#.*hhs%s], %'zu, %s) → %'ld% lm", fd, + MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, + DescribeMsg(flags), rc); return rc; } diff --git a/libc/sock/recvfrom-nt.c b/libc/sock/recvfrom-nt.c index c94c94260..e40e6ed6a 100644 --- a/libc/sock/recvfrom-nt.c +++ b/libc/sock/recvfrom-nt.c @@ -59,14 +59,13 @@ textwindows ssize_t sys_recvfrom_nt(int fd, const struct iovec *iov, return einval(); ssize_t rc; struct Fd *f = g_fds.p + fd; - sigset_t m = __sig_block(); - bool nonblock = (f->flags & O_NONBLOCK) || (flags & _MSG_DONTWAIT); - flags &= ~_MSG_DONTWAIT; - rc = __winsock_block(f->handle, flags, nonblock, f->rcvtimeo, m, - sys_recvfrom_nt_start, + sigset_t waitmask = __sig_block(); + rc = __winsock_block(f->handle, flags & ~_MSG_DONTWAIT, + (f->flags & O_NONBLOCK) || (flags & _MSG_DONTWAIT), + f->rcvtimeo, waitmask, sys_recvfrom_nt_start, &(struct RecvFromArgs){iov, iovlen, opt_out_srcaddr, opt_inout_srcaddrsize}); - __sig_unblock(m); + __sig_unblock(waitmask); return rc; } diff --git a/libc/sock/recvfrom.c b/libc/sock/recvfrom.c index d323b775c..dbcda5b2c 100644 --- a/libc/sock/recvfrom.c +++ b/libc/sock/recvfrom.c @@ -21,6 +21,7 @@ #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.internal.h" #include "libc/dce.h" +#include "libc/intrin/describeflags.h" #include "libc/intrin/strace.h" #include "libc/nt/winsock.h" #include "libc/sock/internal.h" @@ -95,7 +96,11 @@ ssize_t recvfrom(int fd, void *buf, size_t size, int flags, } END_CANCELATION_POINT; - DATATRACE("recvfrom(%d, [%#.*hhs%s], %'zu, %#x) → %'ld% lm", fd, - MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, flags, rc); + DATATRACE( + "recvfrom(%d, [%#.*hhs%s], %'zu, %s, %s) → %'ld% lm", fd, + MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, DescribeMsg(flags), + DescribeSockaddr(opt_out_srcaddr, + opt_inout_srcaddrsize ? *opt_inout_srcaddrsize : 0), + rc); return rc; } diff --git a/libc/sock/send-nt.c b/libc/sock/send-nt.c index b12c0f2b4..ba0570665 100644 --- a/libc/sock/send-nt.c +++ b/libc/sock/send-nt.c @@ -17,20 +17,25 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/internal.h" -#include "libc/calls/struct/iovec.h" +#include "libc/calls/sig.internal.h" +#include "libc/calls/struct/iovec.internal.h" #include "libc/calls/struct/sigset.internal.h" -#include "libc/intrin/fds.h" +#include "libc/errno.h" +#include "libc/nt/errors.h" #include "libc/nt/struct/iovec.h" +#include "libc/nt/struct/overlapped.h" #include "libc/nt/winsock.h" #include "libc/sock/internal.h" -#include "libc/sock/syscall_fd.internal.h" -#include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/sicode.h" +#include "libc/sysv/consts/sig.h" #include "libc/sysv/errfuns.h" +#include "libc/vga/vga.internal.h" #ifdef __x86_64__ #define _MSG_OOB 1 #define _MSG_DONTROUTE 4 #define _MSG_DONTWAIT 64 +#define _MSG_NOSIGNAL 0x10000000 struct SendArgs { const struct iovec *iov; @@ -49,23 +54,25 @@ textwindows static int sys_send_nt_start(int64_t handle, textwindows ssize_t sys_send_nt(int fd, const struct iovec *iov, size_t iovlen, uint32_t flags) { - if (flags & ~(_MSG_DONTWAIT | _MSG_OOB | _MSG_DONTROUTE)) + if (flags & ~(_MSG_DONTWAIT | _MSG_OOB | _MSG_DONTROUTE | _MSG_NOSIGNAL)) return einval(); ssize_t rc; struct Fd *f = g_fds.p + fd; - sigset_t m = __sig_block(); + sigset_t waitmask = __sig_block(); - // we don't check O_NONBLOCK because we want to avoid needing to call - // WSAPoll() every time we write() to a non-blocking socket. WIN32 is - // unsafe at canceling socket sends. lots of code doesn't check write - // return status. good programs that sincerely want to avoid blocking - // on send() operations should have already called poll() beforehand. - bool nonblock = flags & _MSG_DONTWAIT; + rc = __winsock_block(f->handle, flags & ~(_MSG_DONTWAIT | _MSG_NOSIGNAL), + false, f->sndtimeo, waitmask, sys_send_nt_start, + &(struct SendArgs){iov, iovlen}); + + __sig_unblock(waitmask); + + if (rc == -1 && (errno == WSAESHUTDOWN || // ESHUTDOWN + errno == WSAECONNABORTED)) { // ECONNABORTED + errno = kNtErrorBrokenPipe; // EPIPE + if (!(flags & _MSG_NOSIGNAL)) + __sig_raise(SIGPIPE, SI_KERNEL); + } - flags &= ~_MSG_DONTWAIT; - rc = __winsock_block(f->handle, flags, -nonblock, f->sndtimeo, m, - sys_send_nt_start, &(struct SendArgs){iov, iovlen}); - __sig_unblock(m); return rc; } diff --git a/libc/sock/send.c b/libc/sock/send.c index 25c836ddc..81831b27c 100644 --- a/libc/sock/send.c +++ b/libc/sock/send.c @@ -21,6 +21,7 @@ #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.internal.h" #include "libc/dce.h" +#include "libc/intrin/describeflags.h" #include "libc/intrin/strace.h" #include "libc/macros.h" #include "libc/sock/internal.h" @@ -78,7 +79,7 @@ ssize_t send(int fd, const void *buf, size_t size, int flags) { } END_CANCELATION_POINT; - DATATRACE("send(%d, %#.*hhs%s, %'zu, %#x) → %'ld% lm", fd, - MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, flags, rc); + DATATRACE("send(%d, %#.*hhs%s, %'zu, %s) → %'ld% lm", fd, MAX(0, MIN(40, rc)), + buf, rc > 40 ? "..." : "", size, DescribeMsg(flags), rc); return rc; } diff --git a/libc/sock/sendfile.c b/libc/sock/sendfile.c index 17fc36b6a..15a5c03cf 100644 --- a/libc/sock/sendfile.c +++ b/libc/sock/sendfile.c @@ -92,9 +92,10 @@ textwindows dontinline static ssize_t sys_sendfile_nt( } struct NtOverlapped ov = {.hEvent = WSACreateEvent(), .Pointer = offset}; cosmo_once(&g_transmitfile.once, transmitfile_init); - if (g_transmitfile.lpTransmitFile(oh, ih, uptobytes, 0, &ov, 0, 0) || - WSAGetLastError() == kNtErrorIoPending || - WSAGetLastError() == WSAEINPROGRESS) { + if (ov.hEvent && + (g_transmitfile.lpTransmitFile(oh, ih, uptobytes, 0, &ov, 0, 0) || + WSAGetLastError() == kNtErrorIoPending || + WSAGetLastError() == WSAEINPROGRESS)) { if (WSAGetOverlappedResult(oh, &ov, &uptobytes, true, &flags)) { rc = uptobytes; if (opt_in_out_inoffset) { diff --git a/libc/sock/sendto-nt.c b/libc/sock/sendto-nt.c index 41e3520ba..f0be2f4c9 100644 --- a/libc/sock/sendto-nt.c +++ b/libc/sock/sendto-nt.c @@ -17,20 +17,26 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/internal.h" +#include "libc/calls/sig.internal.h" #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/sigset.internal.h" +#include "libc/errno.h" #include "libc/intrin/fds.h" +#include "libc/nt/errors.h" #include "libc/nt/struct/iovec.h" #include "libc/nt/winsock.h" #include "libc/sock/internal.h" #include "libc/sock/syscall_fd.internal.h" #include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/sicode.h" +#include "libc/sysv/consts/sig.h" #include "libc/sysv/errfuns.h" #ifdef __x86_64__ #define _MSG_OOB 1 #define _MSG_DONTROUTE 4 #define _MSG_DONTWAIT 64 +#define _MSG_NOSIGNAL 0x10000000 struct SendToArgs { const struct iovec *iov; @@ -52,17 +58,26 @@ static textwindows int sys_sendto_nt_start(int64_t handle, textwindows ssize_t sys_sendto_nt(int fd, const struct iovec *iov, size_t iovlen, uint32_t flags, void *opt_in_addr, uint32_t in_addrsize) { - if (flags & ~(_MSG_DONTWAIT | _MSG_OOB | _MSG_DONTROUTE)) + if (flags & ~(_MSG_DONTWAIT | _MSG_OOB | _MSG_DONTROUTE | _MSG_NOSIGNAL)) return einval(); ssize_t rc; struct Fd *f = g_fds.p + fd; - sigset_t m = __sig_block(); - bool nonblock = (f->flags & O_NONBLOCK) || (flags & _MSG_DONTWAIT); - flags &= ~_MSG_DONTWAIT; - rc = __winsock_block( - f->handle, flags, -nonblock, f->sndtimeo, m, sys_sendto_nt_start, - &(struct SendToArgs){iov, iovlen, opt_in_addr, in_addrsize}); - __sig_unblock(m); + sigset_t waitmask = __sig_block(); + + rc = __winsock_block(f->handle, flags & ~(_MSG_DONTWAIT | _MSG_NOSIGNAL), + false, f->sndtimeo, waitmask, sys_sendto_nt_start, + &(struct SendToArgs){iov, iovlen, // + opt_in_addr, in_addrsize}); + + __sig_unblock(waitmask); + + if (rc == -1 && (errno == WSAESHUTDOWN || // ESHUTDOWN + errno == WSAECONNABORTED)) { // ECONNABORTED + errno = kNtErrorBrokenPipe; // EPIPE + if (!(flags & _MSG_NOSIGNAL)) + __sig_raise(SIGPIPE, SI_KERNEL); + } + return rc; } diff --git a/libc/sock/sendto.c b/libc/sock/sendto.c index a949c711d..f5a458aeb 100644 --- a/libc/sock/sendto.c +++ b/libc/sock/sendto.c @@ -22,6 +22,7 @@ #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.internal.h" #include "libc/dce.h" +#include "libc/intrin/describeflags.h" #include "libc/intrin/strace.h" #include "libc/macros.h" #include "libc/sock/internal.h" @@ -88,8 +89,8 @@ ssize_t sendto(int fd, const void *buf, size_t size, int flags, } END_CANCELATION_POINT; - DATATRACE("sendto(%d, %#.*hhs%s, %'zu, %#x, %p, %u) → %'ld% lm", fd, - MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, flags, - opt_addr, addrsize, rc); + DATATRACE("sendto(%d, %#.*hhs%s, %'zu, %s, %s) → %'ld% lm", fd, + MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, + DescribeMsg(flags), DescribeSockaddr(opt_addr, addrsize), rc); return rc; } diff --git a/libc/sock/struct/ifreq.h b/libc/sock/struct/ifreq.h index 0f7061f5f..1f6317cb6 100644 --- a/libc/sock/struct/ifreq.h +++ b/libc/sock/struct/ifreq.h @@ -1,6 +1,7 @@ #ifndef COSMOPOLITAN_LIBC_SOCK_STRUCT_IFREQ_H_ #define COSMOPOLITAN_LIBC_SOCK_STRUCT_IFREQ_H_ #include "libc/sock/struct/sockaddr.h" +#include "libc/sock/struct/sockaddr6.h" COSMOPOLITAN_C_START_ #define IF_NAMESIZE 16 @@ -11,6 +12,14 @@ struct ifreq { char ifrn_name[IFNAMSIZ]; /* Interface name, e.g. "en0". */ } ifr_ifrn; union { + struct { + uint16_t sa_family; + uint16_t ifr6_ifindex; /* Interface index */ + uint16_t ifr6_flags; /* Flags */ + uint8_t ifr6_scope; /* Addr scope */ + uint8_t ifr6_prefixlen; /* Prefix length */ + struct in6_addr ifr6_addr; + } in6; struct sockaddr ifru_addr; /* SIOCGIFADDR */ struct sockaddr ifru_dstaddr; /* SIOCGIFDSTADDR */ struct sockaddr ifru_netmask; /* SIOCGIFNETMASK */ @@ -29,5 +38,11 @@ struct ifreq { #define ifr_flags ifr_ifru.ifru_flags /* flags */ #define ifr_ifindex ifr_ifru.ifru_ivalue +#define ifr6_addr ifr_ifru.in6.ifr6_addr /* IP6 Addr */ +#define ifr6_scope ifr_ifru.in6.ifr6_scope /* IP6 Addr scope */ +#define ifr6_prefixlen ifr_ifru.in6.ifr6_prefixlen /* IP6 Prefix length */ +#define ifr6_ifindex ifr_ifru.in6.ifr6_ifindex /* IP6 If index */ +#define ifr6_flags ifr_ifru.in6.ifr6_flags /* IP6 If flags */ + COSMOPOLITAN_C_END_ #endif /* COSMOPOLITAN_LIBC_SOCK_STRUCT_IFREQ_H_ */ diff --git a/libc/sock/struct/pollfd.internal.h b/libc/sock/struct/pollfd.internal.h index 0594fa57c..69f58d30d 100644 --- a/libc/sock/struct/pollfd.internal.h +++ b/libc/sock/struct/pollfd.internal.h @@ -11,7 +11,8 @@ int32_t __sys_poll(struct pollfd *, uint64_t, signed); int sys_ppoll(struct pollfd *, size_t, const struct timespec *, const sigset_t *, size_t); int sys_poll_metal(struct pollfd *, size_t, unsigned); -int sys_poll_nt(struct pollfd *, uint64_t, uint32_t *, const sigset_t *); +int sys_poll_nt(struct pollfd *, uint64_t, const struct timespec *, + const sigset_t *); const char *_DescribePollFds(char[300], ssize_t, struct pollfd *, size_t); #define DescribePollFds(x, y, z) _DescribePollFds(alloca(300), x, y, z) diff --git a/libc/sock/winsockblock.c b/libc/sock/winsockblock.c index 6eb1e702e..32a7e0c82 100644 --- a/libc/sock/winsockblock.c +++ b/libc/sock/winsockblock.c @@ -42,7 +42,7 @@ #ifdef __x86_64__ textwindows ssize_t -__winsock_block(int64_t handle, uint32_t flags, int nonblock, +__winsock_block(int64_t handle, uint32_t flags, bool nonblock, uint32_t srwtimeout, sigset_t waitmask, int StartSocketOp(int64_t handle, struct NtOverlapped *overlap, uint32_t *flags, void *arg), @@ -63,21 +63,6 @@ __winsock_block(int64_t handle, uint32_t flags, int nonblock, bool got_eagain = false; uint32_t other_error = 0; - // send() and sendto() provide O_NONBLOCK as a negative number - // because winsock has a bug that causes CancelIoEx() to cause - // WSAGetOverlappedResult() to report errors when it succeeded - if (nonblock < 0) { - struct sys_pollfd_nt fds[1] = {{handle, POLLOUT}}; - switch (WSAPoll(fds, 1, 0)) { - case -1: - return __winsockerr(); - case 0: - return eagain(); - default: - break; - } - } - // create event handle for overlapped i/o intptr_t event; if (!(event = WSACreateEvent())) @@ -86,7 +71,10 @@ __winsock_block(int64_t handle, uint32_t flags, int nonblock, struct NtOverlapped overlap = {.hEvent = event}; bool32 ok = !StartSocketOp(handle, &overlap, &flags, arg); if (!ok && WSAGetLastError() == kNtErrorIoPending) { - if (nonblock > 0) { + if (nonblock) { + // send() and sendto() shall not pass O_NONBLOCK along to here + // because winsock has a bug that causes CancelIoEx() to cause + // WSAGetOverlappedResult() to report errors when it succeeded CancelIoEx(handle, &overlap); got_eagain = true; } else { diff --git a/libc/stdckdint.h b/libc/stdckdint.h index 2f9afb785..f7117b502 100644 --- a/libc/stdckdint.h +++ b/libc/stdckdint.h @@ -38,14 +38,13 @@ * Instead, you'll get a pretty good pure C11 and C++11 implementation. * * @see https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3096.pdf - * @version 0.1 (2023-07-22) + * @see https://github.com/jart/jtckdint + * @version 1.0 (2024-12-07) */ #define __STDC_VERSION_STDCKDINT_H__ 202311L -#if ((defined(__llvm__) || \ - (defined(__GNUC__) && __GNUC__ * 100 + __GNUC_MINOR__ >= 406)) && \ - !defined(__STRICT_ANSI__)) +#if (!defined(__STRICT_ANSI__) && defined(__SIZEOF_INT128__)) #define __ckd_have_int128 #define __ckd_intmax __int128 #elif ((defined(__cplusplus) && __cplusplus >= 201103L) || \ @@ -58,19 +57,22 @@ typedef signed __ckd_intmax __ckd_intmax_t; typedef unsigned __ckd_intmax __ckd_uintmax_t; -#if (!defined(__STRICT_ANSI__) && \ - ((defined(__GNUC__) && __GNUC__ >= 5 && \ - !defined(__chibicc__) && !defined(__ICC)) || \ - (__has_builtin(__builtin_add_overflow) && \ - __has_builtin(__builtin_sub_overflow) && \ +#if (!defined(__STRICT_ANSI__) && \ + ((defined(__GNUC__) && __GNUC__ >= 5 && !defined(__ICC)) || \ + (__has_builtin(__builtin_add_overflow) && \ + __has_builtin(__builtin_sub_overflow) && \ __has_builtin(__builtin_mul_overflow)))) #define ckd_add(res, x, y) __builtin_add_overflow((x), (y), (res)) #define ckd_sub(res, x, y) __builtin_sub_overflow((x), (y), (res)) #define ckd_mul(res, x, y) __builtin_mul_overflow((x), (y), (res)) -#elif defined(__cplusplus) && __cplusplus >= 201103L -#include "third_party/libcxx/type_traits" -#include "third_party/libcxx/limits" +#elif (defined(__cplusplus) && \ + (__cplusplus >= 201103L || \ + (defined(_MSC_VER) && __cplusplus >= 199711L && \ + __ckd_has_include() && \ + __ckd_has_include()))) +#include +#include template inline bool ckd_add(__T *__res, __U __a, __V __b) { @@ -158,16 +160,6 @@ inline bool ckd_sub(__T *__res, __U __a, __V __b) { __ckd_uintmax_t __y = __b; __ckd_uintmax_t __z = __x - __y; *__res = __z; - if (sizeof(__z) > sizeof(__U) && sizeof(__z) > sizeof(__V)) { - if (sizeof(__z) > sizeof(__T) || std::is_signed<__T>::value) { - return static_cast<__ckd_intmax_t>(__z) != static_cast<__T>(__z); - } else if (!std::is_same<__T, __ckd_uintmax_t>::value) { - return (__z != static_cast<__T>(__z) || - ((std::is_signed<__U>::value || - std::is_signed<__V>::value) && - static_cast<__ckd_intmax_t>(__z) < 0)); - } - } bool __truncated = false; if (sizeof(__T) < sizeof(__ckd_intmax_t)) { __truncated = __z != static_cast<__ckd_uintmax_t>(static_cast<__T>(__z)); @@ -266,8 +258,8 @@ inline bool ckd_mul(__T *__res, __U __a, __V __b) { case 3: { // u = s * s int __o = false; if (static_cast<__ckd_intmax_t>(__x & __y) < 0) { - __x = -__x; - __y = -__y; + __x = 0 - __x; + __y = 0 - __y; } else if (static_cast<__ckd_intmax_t>(__x ^ __y) < 0) { __o = __x && __y; } @@ -286,12 +278,12 @@ inline bool ckd_mul(__T *__res, __U __a, __V __b) { __z != static_cast<__ckd_uintmax_t>(*__res))); } case 5: { // s = u * s - __ckd_uintmax_t __t = -__y; + __ckd_uintmax_t __t = 0 - __y; __t = static_cast<__ckd_intmax_t>(__t) < 0 ? __y : __t; __ckd_uintmax_t __p = __t * __x; int __o = __t && __p / __t != __x; int __n = static_cast<__ckd_intmax_t>(__y) < 0; - __ckd_uintmax_t __z = __n ? -__p : __p; + __ckd_uintmax_t __z = __n ? 0 - __p : __p; *__res = __z; __ckd_uintmax_t __m = std::numeric_limits<__ckd_intmax_t>::max(); return (__o | (__p > __m + __n) | @@ -299,12 +291,12 @@ inline bool ckd_mul(__T *__res, __U __a, __V __b) { __z != static_cast<__ckd_uintmax_t>(*__res))); } case 6: { // s = s * u - __ckd_uintmax_t __t = -__x; + __ckd_uintmax_t __t = 0 - __x; __t = static_cast<__ckd_intmax_t>(__t) < 0 ? __x : __t; __ckd_uintmax_t __p = __t * __y; int __o = __t && __p / __t != __y; int __n = static_cast<__ckd_intmax_t>(__x) < 0; - __ckd_uintmax_t __z = __n ? -__p : __p; + __ckd_uintmax_t __z = __n ? 0 - __p : __p; *__res = __z; __ckd_uintmax_t __m = std::numeric_limits<__ckd_intmax_t>::max(); return (__o | (__p > __m + __n) | @@ -540,8 +532,8 @@ __ckd_declare_sub(__ckd_sub_uint128, unsigned __int128) case 3: { /* u = s * s */ \ int __o = 0; \ if ((__ckd_intmax_t)(__x & __y) < 0) { \ - __x = -__x; \ - __y = -__y; \ + __x = 0 - __x; \ + __y = 0 - __y; \ } else if ((__ckd_intmax_t)(__x ^ __y) < 0) { \ __o = __x && __y; \ } \ @@ -560,12 +552,12 @@ __ckd_declare_sub(__ckd_sub_uint128, unsigned __int128) __z != (__ckd_uintmax_t)*(T *)__res)); \ } \ case 5: { /* s = u * s */ \ - __ckd_uintmax_t __t = -__y; \ + __ckd_uintmax_t __t = 0 - __y; \ __t = (__ckd_intmax_t)(__t) < 0 ? __y : __t; \ __ckd_uintmax_t __p = __t * __x; \ int __o = __t && __p / __t != __x; \ int __n = (__ckd_intmax_t)__y < 0; \ - __ckd_uintmax_t __z = __n ? -__p : __p; \ + __ckd_uintmax_t __z = __n ? 0 - __p : __p; \ *(T *)__res = __z; \ __ckd_uintmax_t __m = __ckd_sign(__ckd_uintmax_t) - 1; \ return (__o | (__p > __m + __n) | \ @@ -573,12 +565,12 @@ __ckd_declare_sub(__ckd_sub_uint128, unsigned __int128) __z != (__ckd_uintmax_t)*(T *)__res)); \ } \ case 6: { /* s = s * u */ \ - __ckd_uintmax_t __t = -__x; \ + __ckd_uintmax_t __t = 0 - __x; \ __t = (__ckd_intmax_t)(__t) < 0 ? __x : __t; \ __ckd_uintmax_t __p = __t * __y; \ int __o = __t && __p / __t != __y; \ int __n = (__ckd_intmax_t)__x < 0; \ - __ckd_uintmax_t __z = __n ? -__p : __p; \ + __ckd_uintmax_t __z = __n ? 0 - __p : __p; \ *(T *)__res = __z; \ __ckd_uintmax_t __m = __ckd_sign(__ckd_uintmax_t) - 1; \ return (__o | (__p > __m + __n) | \ diff --git a/libc/stdio/BUILD.mk b/libc/stdio/BUILD.mk index 069e5cf08..c4d60fc7f 100644 --- a/libc/stdio/BUILD.mk +++ b/libc/stdio/BUILD.mk @@ -32,12 +32,13 @@ LIBC_STDIO_A_DIRECTDEPS = \ LIBC_NEXGEN32E \ LIBC_NT_ADVAPI32 \ LIBC_NT_KERNEL32 \ - LIBC_RUNTIME \ LIBC_PROC \ + LIBC_RUNTIME \ LIBC_STR \ LIBC_SYSV \ LIBC_SYSV_CALLS \ - THIRD_PARTY_GDTOA + THIRD_PARTY_DLMALLOC \ + THIRD_PARTY_GDTOA \ LIBC_STDIO_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_STDIO_A_DIRECTDEPS),$($(x)))) diff --git a/libc/stdio/alloc.c b/libc/stdio/alloc.c index ce348098d..ace00fa2f 100644 --- a/libc/stdio/alloc.c +++ b/libc/stdio/alloc.c @@ -22,20 +22,14 @@ FILE *__stdio_alloc(void) { FILE *f; + __stdio_lock(); if ((f = calloc(1, sizeof(FILE)))) { - pthread_mutexattr_t attr; - pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); - pthread_mutex_init(&f->lock, &attr); - pthread_mutexattr_destroy(&attr); - f->dynamic = 1; + f->freethis = 1; + f->fd = -1; + f->lock = (pthread_mutex_t)PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; + dll_init(&f->elem); + dll_make_last(&__stdio.files, &f->elem); } + __stdio_unlock(); return f; } - -void __stdio_free(FILE *f) { - pthread_mutex_destroy(&f->lock); - if (f->dynamic) { - free(f); - } -} diff --git a/libc/stdio/dirstream.c b/libc/stdio/dirstream.c index ef11b674b..f77f4e06c 100644 --- a/libc/stdio/dirstream.c +++ b/libc/stdio/dirstream.c @@ -49,6 +49,7 @@ #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/s.h" #include "libc/sysv/errfuns.h" +#include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" #include "libc/thread/tls.h" #include "libc/zip.h" @@ -134,11 +135,11 @@ struct dirent_netbsd { }; static void lockdir(DIR *dir) { - pthread_mutex_lock(&dir->lock); + _pthread_mutex_lock(&dir->lock); } static void unlockdir(DIR *dir) { - pthread_mutex_unlock(&dir->lock); + _pthread_mutex_unlock(&dir->lock); } static textwindows dontinline int fdopendir_nt(DIR *res, int fd) { diff --git a/libc/stdio/fclose.c b/libc/stdio/fclose.c index b02c8bf20..2fcf0f790 100644 --- a/libc/stdio/fclose.c +++ b/libc/stdio/fclose.c @@ -16,47 +16,26 @@ │ 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/errno.h" -#include "libc/intrin/weaken.h" -#include "libc/mem/mem.h" -#include "libc/runtime/runtime.h" #include "libc/stdio/internal.h" -#include "libc/stdio/stdio.h" /** * Closes standard i/o stream and its underlying thing. - * - * @param f is the file object - * @return 0 on success or -1 on error, which can be a trick for - * differentiating between EOF and real errors during previous - * i/o calls, without needing to call ferror() + * @return 0 on success, or EOF w/ errno */ int fclose(FILE *f) { - int rc; - if (!f) - return 0; - __fflush_unregister(f); - fflush(f); - if (_weaken(free)) { - _weaken(free)(f->getln); - if (!f->nofree && f->buf != f->mem) { - _weaken(free)(f->buf); - } - } - f->state = EOF; - if (f->noclose) { + int rc = 0; + if (f) { + flockfile(f); + rc |= fflush(f); + int fd = f->fd; f->fd = -1; - } else if (f->fd != -1 && close(f->fd) == -1) { - f->state = errno; + f->state = EOF; + if (fd != -1) + rc |= close(fd); + funlockfile(f); + __stdio_unref(f); } - if (f->state == EOF) { - rc = 0; - } else { - errno = f->state; - rc = EOF; - } - __stdio_free(f); return rc; } diff --git a/libc/stdio/fdopen.c b/libc/stdio/fdopen.c index eb4437a0a..5f7191d07 100644 --- a/libc/stdio/fdopen.c +++ b/libc/stdio/fdopen.c @@ -16,14 +16,12 @@ │ 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/stat.h" +#include "libc/mem/mem.h" #include "libc/stdio/internal.h" -#include "libc/stdio/stdio.h" -#include "libc/sysv/consts/o.h" #include "libc/sysv/consts/s.h" -#include "libc/sysv/errfuns.h" -#include "libc/thread/thread.h" + +__static_yoink("fflush"); /** * Allocates stream object for already-opened file descriptor. @@ -38,16 +36,16 @@ FILE *fdopen(int fd, const char *mode) { struct stat st; if (fstat(fd, &st)) return 0; - if ((f = __stdio_alloc())) { - f->fd = fd; - f->bufmode = S_ISREG(st.st_mode) ? _IOFBF : _IONBF; - f->iomode = fopenflags(mode); - f->buf = f->mem; - f->size = BUFSIZ; - if ((f->iomode & O_ACCMODE) != O_RDONLY) { - __fflush_register(f); - } - return f; + if (!(f = __stdio_alloc())) + return 0; + f->bufmode = S_ISCHR(st.st_mode) ? _IONBF : _IOFBF; + f->oflags = fopenflags(mode); + f->size = BUFSIZ; + if (!(f->buf = malloc(f->size))) { + __stdio_unref(f); + return 0; } - return NULL; + f->freebuf = 1; + f->fd = fd; + return f; } diff --git a/libc/stdio/fflush.c b/libc/stdio/fflush.c index 4a9ef6c8e..4a408d313 100644 --- a/libc/stdio/fflush.c +++ b/libc/stdio/fflush.c @@ -16,20 +16,38 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/stdio/stdio.h" +#include "libc/cxxabi.h" +#include "libc/stdio/internal.h" /** * Blocks until data from stream buffer is written out. * * @param f is the stream handle, or 0 for all streams - * @return is 0 on success or -1 on error + * @return is 0 on success or EOF on error */ int fflush(FILE *f) { int rc; - if (f) + if (f) { flockfile(f); - rc = fflush_unlocked(f); - if (f) + rc = fflush_unlocked(f); funlockfile(f); + } else { + __stdio_lock(); + struct Dll *e, *e2; + for (rc = 0, e = dll_last(__stdio.files); e; e = e2) { + f = FILE_CONTAINER(e); + __stdio_ref(f); + __stdio_unlock(); + rc |= fflush(FILE_CONTAINER(e)); + __stdio_lock(); + e2 = dll_prev(__stdio.files, e); + __stdio_unref_unlocked(f); + } + __stdio_unlock(); + } return rc; } + +__attribute__((__constructor__(60))) static textstartup void fflush_init(void) { + __cxa_atexit((void *)fflush, 0, 0); +} diff --git a/libc/stdio/fflush.internal.h b/libc/stdio/fflush.internal.h deleted file mode 100644 index 75e3f3fc2..000000000 --- a/libc/stdio/fflush.internal.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_STDIO_FFLUSH_H_ -#define COSMOPOLITAN_LIBC_STDIO_FFLUSH_H_ -#include "libc/stdio/stdio.h" -#include "libc/thread/thread.h" -#include "libc/thread/tls.h" -COSMOPOLITAN_C_START_ - -struct StdioFlushHandles { - size_t i, n; - FILE **p; -}; - -struct StdioFlush { - struct StdioFlushHandles handles; - FILE *handles_initmem[8]; -}; - -extern struct StdioFlush __fflush; -extern pthread_mutex_t __fflush_lock_obj; - -void __fflush_lock(void); -void __fflush_unlock(void); - -COSMOPOLITAN_C_END_ -#endif /* COSMOPOLITAN_LIBC_STDIO_FFLUSH_H_ */ diff --git a/libc/stdio/fflush_unlocked.c b/libc/stdio/fflush_unlocked.c index 49099d7e7..9532bf9d5 100644 --- a/libc/stdio/fflush_unlocked.c +++ b/libc/stdio/fflush_unlocked.c @@ -16,75 +16,46 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/cxxabi.h" -#include "libc/intrin/pushpop.h" -#include "libc/mem/arraylist.internal.h" -#include "libc/stdio/fflush.internal.h" +#include "libc/calls/calls.h" +#include "libc/errno.h" +#include "libc/intrin/weaken.h" +#include "libc/mem/mem.h" #include "libc/stdio/internal.h" +#include "libc/sysv/consts/o.h" /** * Blocks until data from stream buffer is written out. * - * @param f is the stream handle, or 0 for all streams - * @return is 0 on success or -1 on error + * @param f is the stream handle, which must not be null + * @return is 0 on success or EOF on error */ int fflush_unlocked(FILE *f) { - int rc = 0; size_t i; - if (!f) { - __fflush_lock(); - for (i = __fflush.handles.i; i; --i) { - if ((f = __fflush.handles.p[i - 1])) { - if (fflush(f) == -1) { - rc = -1; + if (f->getln) { + if (_weaken(free)) + _weaken(free)(f->getln); + f->getln = 0; + } + if (f->fd != -1) { + if (f->beg && !f->end && (f->oflags & O_ACCMODE) != O_RDONLY) { + ssize_t rc; + for (i = 0; i < f->beg; i += rc) { + if ((rc = write(f->fd, f->buf + i, f->beg - i)) == -1) { + f->state = errno; + return EOF; } } + f->beg = 0; } - __fflush_unlock(); - } else if (f->fd != -1) { - if (__fflush_impl(f) == -1) { - rc = -1; + if (f->beg < f->end && (f->oflags & O_ACCMODE) != O_WRONLY) { + if (lseek(f->fd, -(int)(f->end - f->beg), SEEK_CUR) == -1) { + f->state = errno; + return EOF; + } + f->end = f->beg; } - } else if (f->beg && f->beg < f->size) { + } + if (f->buf && f->beg && f->beg < f->size) f->buf[f->beg] = 0; - } - return rc; -} - -textstartup int __fflush_register(FILE *f) { - int rc; - size_t i; - struct StdioFlush *sf; - __fflush_lock(); - sf = &__fflush; - if (!sf->handles.p) { - sf->handles.p = sf->handles_initmem; - pushmov(&sf->handles.n, ARRAYLEN(sf->handles_initmem)); - __cxa_atexit((void *)fflush_unlocked, 0, 0); - } - for (i = sf->handles.i; i; --i) { - if (!sf->handles.p[i - 1]) { - sf->handles.p[i - 1] = f; - __fflush_unlock(); - return 0; - } - } - rc = append(&sf->handles, &f); - __fflush_unlock(); - return rc; -} - -void __fflush_unregister(FILE *f) { - size_t i; - struct StdioFlush *sf; - __fflush_lock(); - sf = &__fflush; - sf = pushpop(sf); - for (i = sf->handles.i; i; --i) { - if (sf->handles.p[i - 1] == f) { - pushmov(&sf->handles.p[i - 1], 0); - break; - } - } - __fflush_unlock(); + return 0; } diff --git a/libc/stdio/flockfile.c b/libc/stdio/flockfile.c index 06bfe2359..4b16a0778 100644 --- a/libc/stdio/flockfile.c +++ b/libc/stdio/flockfile.c @@ -17,10 +17,8 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" -#include "libc/stdio/fflush.internal.h" #include "libc/stdio/internal.h" -#include "libc/stdio/stdio.h" -#include "libc/str/str.h" +#include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" /** @@ -28,41 +26,5 @@ */ void flockfile(FILE *f) { unassert(f != NULL); - pthread_mutex_lock(&f->lock); -} - -void(__fflush_lock)(void) { - pthread_mutex_lock(&__fflush_lock_obj); -} - -void(__fflush_unlock)(void) { - pthread_mutex_unlock(&__fflush_lock_obj); -} - -static void __stdio_fork_prepare(void) { - FILE *f; - __fflush_lock(); - for (int i = 0; i < __fflush.handles.i; ++i) - if ((f = __fflush.handles.p[i])) - pthread_mutex_lock(&f->lock); -} - -static void __stdio_fork_parent(void) { - FILE *f; - for (int i = __fflush.handles.i; i--;) - if ((f = __fflush.handles.p[i])) - pthread_mutex_unlock(&f->lock); - __fflush_unlock(); -} - -static void __stdio_fork_child(void) { - FILE *f; - for (int i = __fflush.handles.i; i--;) - if ((f = __fflush.handles.p[i])) - f->lock = (pthread_mutex_t)PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; - pthread_mutex_init(&__fflush_lock_obj, 0); -} - -__attribute__((__constructor__(60))) static textstartup void stdioinit(void) { - pthread_atfork(__stdio_fork_prepare, __stdio_fork_parent, __stdio_fork_child); + _pthread_mutex_lock(&f->lock); } diff --git a/libc/stdio/flushlbf.c b/libc/stdio/flushlbf.c index 53a7d1a80..860e093b1 100644 --- a/libc/stdio/flushlbf.c +++ b/libc/stdio/flushlbf.c @@ -17,7 +17,6 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" -#include "libc/stdio/fflush.internal.h" #include "libc/stdio/internal.h" #include "libc/stdio/stdio.h" #include "libc/stdio/stdio_ext.h" @@ -26,17 +25,18 @@ * Flushes all line-buffered streams. */ void _flushlbf(void) { - int i; - FILE *f; - __fflush_lock(); - for (i = 0; i < __fflush.handles.i; ++i) { - if ((f = __fflush.handles.p[i])) { - flockfile(f); - if (f->bufmode == _IOLBF) { - fflush_unlocked(f); - } - funlockfile(f); + __stdio_lock(); + struct Dll *e, *e2; + for (e = dll_last(__stdio.files); e; e = e2) { + FILE *f = FILE_CONTAINER(e); + if (f->bufmode == _IOLBF) { + __stdio_ref(f); + __stdio_unlock(); + fflush(FILE_CONTAINER(e)); + __stdio_lock(); + e2 = dll_prev(__stdio.files, e); + __stdio_unref_unlocked(f); } } - __fflush_unlock(); + __stdio_unlock(); } diff --git a/libc/stdio/fmemopen.c b/libc/stdio/fmemopen.c index 21945de76..3834a7d1e 100644 --- a/libc/stdio/fmemopen.c +++ b/libc/stdio/fmemopen.c @@ -37,36 +37,31 @@ FILE *fmemopen(void *buf, size_t size, const char *mode) { FILE *f; char *p; - int iomode; - iomode = fopenflags(mode); + int oflags; + oflags = fopenflags(mode); if ((size && size > 0x7ffff000) || // - (!buf && (iomode & O_ACCMODE) != O_RDWR)) { + (!buf && (oflags & O_ACCMODE) != O_RDWR)) { einval(); return NULL; } - if (!(f = __stdio_alloc())) { + if (!(f = __stdio_alloc())) return NULL; - } - if (buf) { - f->nofree = true; - } else { + if (!buf) { if (!size) size = BUFSIZ; - // TODO(jart): Why do we need calloc()? - if (!_weaken(calloc) || !(buf = _weaken(calloc)(1, size))) { - __stdio_free(f); + if (!(buf = malloc(size))) { + __stdio_unref(f); enomem(); return NULL; } + f->freebuf = 1; } - f->fd = -1; f->buf = buf; - if (!(iomode & O_TRUNC)) { + if (!(oflags & O_TRUNC)) f->end = size; - } f->size = size; - f->iomode = iomode; - if (iomode & O_APPEND) { + f->oflags = oflags; + if (oflags & O_APPEND) { if ((p = memchr(buf, '\0', size))) { f->beg = p - (char *)buf; } else { diff --git a/libc/stdio/fmt.c b/libc/stdio/fmt.c index d309cf629..5f476e1a0 100644 --- a/libc/stdio/fmt.c +++ b/libc/stdio/fmt.c @@ -1090,9 +1090,10 @@ int __fmt(void *fn, void *arg, const char *format, va_list va, int *wrote) { case 'x': log2base = 4; goto FormatNumber; + case 'B': case 'b': log2base = 1; - alphabet = "0123456789abcdefpb"; + alphabet = (d == 'b' ? "0123456789abcdefpb" : "0123456789ABCDEFPB"); goto FormatNumber; case 'o': log2base = 3; diff --git a/libc/stdio/fopen.c b/libc/stdio/fopen.c index 0294c5c2d..d077f3c01 100644 --- a/libc/stdio/fopen.c +++ b/libc/stdio/fopen.c @@ -17,36 +17,9 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" -#include "libc/mem/mem.h" -#include "libc/stdio/internal.h" #include "libc/stdio/stdio.h" -#include "libc/str/str.h" -#include "libc/sysv/consts/o.h" -#include "libc/sysv/errfuns.h" -static const char *fixpathname(const char *pathname, int flags) { - if ((flags & O_ACCMODE) == O_RDONLY && strcmp(pathname, "-") == 0) { - return "/dev/stdin"; - } else if ((flags & O_ACCMODE) == O_WRONLY && strcmp(pathname, "-") == 0) { - return "/dev/stdout"; - } else { - return pathname; - } -} - -static int openpathname(const char *pathname, int flags, bool *out_noclose) { - if ((flags & O_ACCMODE) == O_RDONLY && strcmp(pathname, "/dev/stdin") == 0) { - *out_noclose = true; - return fileno(stdin); - } else if ((flags & O_ACCMODE) == O_WRONLY && - strcmp(pathname, "/dev/stdout") == 0) { - *out_noclose = true; - return fileno(stdout); - } else { - *out_noclose = false; - return open(pathname, flags, 0666); - } -} +__static_yoink("fflush"); /** * Opens file as stream object. @@ -57,21 +30,13 @@ static int openpathname(const char *pathname, int flags, bool *out_noclose) { * @note microsoft unilaterally deprecated this function lool */ FILE *fopen(const char *pathname, const char *mode) { - FILE *f = 0; - bool noclose; - int fd, flags; - if (!pathname) { - efault(); + int fd; + if ((fd = open(pathname, fopenflags(mode), 0666)) == -1) + return 0; + FILE *f; + if (!(f = fdopen(fd, mode))) { + close(fd); return 0; - } - flags = fopenflags(mode); - pathname = fixpathname(pathname, flags); - if ((fd = openpathname(pathname, flags, &noclose)) != -1) { - if ((f = fdopen(fd, mode)) != NULL) { - f->noclose = noclose; - } else if (!noclose) { - close(fd); - } } return f; } diff --git a/libc/stdio/fread_unlocked.c b/libc/stdio/fread_unlocked.c index ef341d8ec..98179bf52 100644 --- a/libc/stdio/fread_unlocked.c +++ b/libc/stdio/fread_unlocked.c @@ -86,7 +86,7 @@ size_t fread_unlocked(void *buf, size_t stride, size_t count, FILE *f) { size_t n, m, got, need; // check state and parameters - if ((f->iomode & O_ACCMODE) == O_WRONLY) { + if ((f->oflags & O_ACCMODE) == O_WRONLY) { f->state = errno = EBADF; return 0; } diff --git a/libc/stdio/freadable.c b/libc/stdio/freadable.c index ff78a7a84..8a623623a 100644 --- a/libc/stdio/freadable.c +++ b/libc/stdio/freadable.c @@ -24,6 +24,6 @@ * Returns nonzero if stream allows reading. */ int __freadable(FILE *f) { - return (f->iomode & O_ACCMODE) == O_RDONLY || - (f->iomode & O_ACCMODE) == O_RDWR; + return (f->oflags & O_ACCMODE) == O_RDONLY || + (f->oflags & O_ACCMODE) == O_RDWR; } diff --git a/libc/stdio/freading.c b/libc/stdio/freading.c index 2e3782b3f..0f447bf5b 100644 --- a/libc/stdio/freading.c +++ b/libc/stdio/freading.c @@ -24,5 +24,5 @@ * Returns nonzero if stream is read only. */ int __freading(FILE *f) { - return (f->iomode & O_ACCMODE) == O_RDONLY; + return (f->oflags & O_ACCMODE) == O_RDONLY; } diff --git a/libc/stdio/freopen.c b/libc/stdio/freopen.c index 6c2902163..c2db89e60 100644 --- a/libc/stdio/freopen.c +++ b/libc/stdio/freopen.c @@ -51,7 +51,7 @@ FILE *freopen(const char *pathname, const char *mode, FILE *stream) { close(fd); if (fd2 != -1) { stream->fd = fd2; - stream->iomode = flags; + stream->oflags = flags; stream->beg = 0; stream->end = 0; res = stream; diff --git a/libc/stdio/fseek_unlocked.c b/libc/stdio/fseek_unlocked.c index f2acddc26..b3fd2bbbe 100644 --- a/libc/stdio/fseek_unlocked.c +++ b/libc/stdio/fseek_unlocked.c @@ -34,13 +34,13 @@ * @param f is a non-null stream handle * @param offset is the byte delta * @param whence can be SEET_SET, SEEK_CUR, or SEEK_END - * @returns 0 on success or -1 on error + * @returns 0 on success or -1 w/ errno */ int fseek_unlocked(FILE *f, int64_t offset, int whence) { int res; int64_t pos; if (f->fd != -1) { - if (__fflush_impl(f) == -1) + if (fflush_unlocked(f) == EOF) return -1; if (whence == SEEK_CUR && f->beg < f->end) { offset -= f->end - f->beg; diff --git a/libc/stdio/ftell.c b/libc/stdio/ftell.c index 7330e35d6..103a7d217 100644 --- a/libc/stdio/ftell.c +++ b/libc/stdio/ftell.c @@ -26,7 +26,7 @@ static inline int64_t ftell_unlocked(FILE *f) { int64_t pos; if (f->fd != -1) { - if (__fflush_impl(f) == -1) + if (fflush_unlocked(f) == EOF) return -1; if ((pos = lseek(f->fd, 0, SEEK_CUR)) != -1) { if (f->beg < f->end) diff --git a/libc/stdio/funlockfile.c b/libc/stdio/funlockfile.c index b47f8ab9d..cfeb7f534 100644 --- a/libc/stdio/funlockfile.c +++ b/libc/stdio/funlockfile.c @@ -18,11 +18,12 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/stdio/internal.h" #include "libc/stdio/stdio.h" +#include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" /** * Releases lock on stdio object. */ void funlockfile(FILE *f) { - pthread_mutex_unlock(&f->lock); + _pthread_mutex_unlock(&f->lock); } diff --git a/libc/stdio/fwritable.c b/libc/stdio/fwritable.c index df10a0aea..ef1205bb8 100644 --- a/libc/stdio/fwritable.c +++ b/libc/stdio/fwritable.c @@ -24,6 +24,6 @@ * Returns nonzero if stream allows reading. */ int __fwritable(FILE *f) { - return (f->iomode & O_ACCMODE) == O_WRONLY || - (f->iomode & O_ACCMODE) == O_RDWR; + return (f->oflags & O_ACCMODE) == O_WRONLY || + (f->oflags & O_ACCMODE) == O_RDWR; } diff --git a/libc/stdio/fwrite_unlocked.c b/libc/stdio/fwrite_unlocked.c index 4fa7c4d04..ed1cc7c39 100644 --- a/libc/stdio/fwrite_unlocked.c +++ b/libc/stdio/fwrite_unlocked.c @@ -74,10 +74,9 @@ size_t fwrite_unlocked(const void *data, size_t stride, size_t count, FILE *f) { size_t n, m; const char *p; struct iovec iov[2]; - if (!stride || !count) { + if (!stride || !count) return 0; - } - if ((f->iomode & O_ACCMODE) == O_RDONLY) { + if ((f->oflags & O_ACCMODE) == O_RDONLY) { f->state = errno = EBADF; return 0; } diff --git a/libc/stdio/fwriting.c b/libc/stdio/fwriting.c index 8a4f012a1..8a755bcb2 100644 --- a/libc/stdio/fwriting.c +++ b/libc/stdio/fwriting.c @@ -24,5 +24,5 @@ * Returns nonzero if stream is write only. */ int __fwriting(FILE *f) { - return (f->iomode & O_ACCMODE) == O_WRONLY; + return (f->oflags & O_ACCMODE) == O_WRONLY; } diff --git a/libc/stdio/getdelim_unlocked.c b/libc/stdio/getdelim_unlocked.c index 036017097..569040836 100644 --- a/libc/stdio/getdelim_unlocked.c +++ b/libc/stdio/getdelim_unlocked.c @@ -32,7 +32,7 @@ ssize_t getdelim_unlocked(char **s, size_t *n, int delim, FILE *f) { ssize_t rc; char *p, *s2; size_t i, m, n2; - if ((f->iomode & O_ACCMODE) == O_WRONLY) { + if ((f->oflags & O_ACCMODE) == O_WRONLY) { f->state = errno = EBADF; return -1; } @@ -44,9 +44,8 @@ ssize_t getdelim_unlocked(char **s, size_t *n, int delim, FILE *f) { *n = 0; for (i = 0;; i += m) { m = f->end - f->beg; - if ((p = memchr(f->buf + f->beg, delim, m))) { + if ((p = memchr(f->buf + f->beg, delim, m))) m = p + 1 - (f->buf + f->beg); - } if (i + m + 1 > *n) { n2 = i + m + 1; s2 = realloc(*s, n2); @@ -59,10 +58,9 @@ ssize_t getdelim_unlocked(char **s, size_t *n, int delim, FILE *f) { } } memcpy(*s + i, f->buf + f->beg, m); - (*s)[i + m] = '\0'; - if ((f->beg += m) == f->end) { + (*s)[i + m] = 0; + if ((f->beg += m) == f->end) f->beg = f->end = 0; - } if (p) { return i + m; } else if (f->fd == -1) { @@ -71,7 +69,7 @@ ssize_t getdelim_unlocked(char **s, size_t *n, int delim, FILE *f) { if (!rc) break; f->end = rc; - } else if (errno != EINTR) { + } else { f->state = errno; return -1; } diff --git a/libc/stdio/getentropy.c b/libc/stdio/getentropy.c index 45b5b918e..ad8d357a8 100644 --- a/libc/stdio/getentropy.c +++ b/libc/stdio/getentropy.c @@ -17,17 +17,17 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/blockcancel.internal.h" -#include "libc/calls/struct/sigset.internal.h" #include "libc/calls/syscall_support-sysv.internal.h" #include "libc/dce.h" +#include "libc/errno.h" #include "libc/intrin/strace.h" -#include "libc/stdio/rand.h" +#include "libc/runtime/syslib.internal.h" #include "libc/sysv/errfuns.h" int sys_getentropy(void *, size_t) asm("sys_getrandom"); /** - * Returns random seeding bytes, the XNU/OpenBSD way. + * Returns random seeding bytes, the POSIX way. * * @return 0 on success, or -1 w/ errno * @raise EFAULT if the `n` bytes at `p` aren't valid memory @@ -40,18 +40,26 @@ int getentropy(void *p, size_t n) { rc = eio(); } else if ((!p && n)) { rc = efault(); + } else if (IsXnuSilicon()) { + rc = __syslib->__getentropy(p, n); } else if (IsXnu() || IsOpenbsd()) { - if (sys_getentropy(p, n)) - notpossible; - rc = 0; + rc = sys_getentropy(p, n); } else { - BLOCK_SIGNALS; + ssize_t got; BLOCK_CANCELATION; - if (__getrandom(p, n, 0) != n) - notpossible; - ALLOW_CANCELATION; - ALLOW_SIGNALS; rc = 0; + for (size_t i = 0; i < n; i += got) { + got = __getrandom(p + i, n - i, 0); + if (got == -1) { + if (errno == EAGAIN || errno == EINTR) { + got = 0; + } else { + rc = -1; + break; + } + } + } + ALLOW_CANCELATION; } STRACE("getentropy(%p, %'zu) → %'ld% m", p, n, rc); return rc; diff --git a/libc/stdio/internal.h b/libc/stdio/internal.h index e5f848f80..2f4857a71 100644 --- a/libc/stdio/internal.h +++ b/libc/stdio/internal.h @@ -1,39 +1,49 @@ #ifndef COSMOPOLITAN_LIBC_STDIO_INTERNAL_H_ #define COSMOPOLITAN_LIBC_STDIO_INTERNAL_H_ +#include "libc/atomic.h" +#include "libc/intrin/dll.h" #include "libc/stdio/stdio.h" #include "libc/thread/thread.h" #define PUSHBACK 12 +#define FILE_CONTAINER(e) DLL_CONTAINER(struct FILE, elem, e) + COSMOPOLITAN_C_START_ struct FILE { - uint8_t bufmode; /* _IOFBF, etc. (ignored if fd=-1) */ - char noclose; /* for fake dup() todo delete! */ - char dynamic; /* did malloc() create this object? */ - uint32_t iomode; /* O_RDONLY, etc. (ignored if fd=-1) */ - int32_t state; /* 0=OK, -1=EOF, >0=errno */ - int fd; /* ≥0=fd, -1=closed|buffer */ - uint32_t beg; - uint32_t end; - char *buf; - uint32_t size; - uint32_t nofree; + char bufmode; /* _IOFBF, _IOLBF, or _IONBF */ + char freethis; /* fclose() should free(this) */ + char freebuf; /* fclose() should free(this->buf) */ + char forking; /* used by fork() implementation */ + int oflags; /* O_RDONLY, etc. */ + int state; /* 0=OK, -1=EOF, >0=errno */ + int fd; /* ≥0=fd, -1=closed|buffer */ int pid; - char *getln; + atomic_int refs; + unsigned size; + unsigned beg; + unsigned end; + char *buf; pthread_mutex_t lock; - struct FILE *next; - char mem[BUFSIZ]; + struct Dll elem; + char *getln; }; -extern uint64_t g_rando; +struct Stdio { + pthread_mutex_t lock; /* Subordinate to FILE::lock */ + struct Dll *files; +}; -int __fflush_impl(FILE *); -int __fflush_register(FILE *); -void __fflush_unregister(FILE *); +extern struct Stdio __stdio; + +void __stdio_lock(void); +void __stdio_unlock(void); +void __stdio_ref(FILE *); +void __stdio_unref(FILE *); +void __stdio_unref_unlocked(FILE *); bool __stdio_isok(FILE *); FILE *__stdio_alloc(void); -void __stdio_free(FILE *); COSMOPOLITAN_C_END_ #endif /* COSMOPOLITAN_LIBC_STDIO_INTERNAL_H_ */ diff --git a/libc/stdio/rand.c b/libc/stdio/rand.c index 1802d99b2..1a5aad654 100644 --- a/libc/stdio/rand.c +++ b/libc/stdio/rand.c @@ -17,9 +17,17 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/stdio/rand.h" -#include "libc/stdio/internal.h" #include "libc/stdio/lcg.internal.h" +static uint64_t rando; + +/** + * Seeds random number generator that's used by rand(). + */ +void srand(unsigned seed) { + rando = seed; +} + /** * Returns 31-bit linear congruential pseudorandom number, e.g. * @@ -39,5 +47,5 @@ * @threadunsafe */ int rand(void) { - return KnuthLinearCongruentialGenerator(&g_rando) >> 33; + return KnuthLinearCongruentialGenerator(&rando) >> 33; } diff --git a/libc/stdio/setvbuf.c b/libc/stdio/setvbuf.c index 6be7ca74b..e3ef1d1fb 100644 --- a/libc/stdio/setvbuf.c +++ b/libc/stdio/setvbuf.c @@ -38,15 +38,13 @@ int setvbuf(FILE *f, char *buf, int mode, size_t size) { if (buf) { if (!size) size = BUFSIZ; - if (!f->nofree && // - f->buf != buf && // - f->buf != f->mem && // - _weaken(free)) { - _weaken(free)(f->buf); - } + if (f->freebuf) + if (f->buf != buf) + if (_weaken(free)) + _weaken(free)(f->buf); f->buf = buf; f->size = size; - f->nofree = true; + f->freebuf = 0; } f->bufmode = mode; funlockfile(f); diff --git a/libc/stdio/stderr.c b/libc/stdio/stderr.c index de694ed62..72af4c828 100644 --- a/libc/stdio/stderr.c +++ b/libc/stdio/stderr.c @@ -1,5 +1,5 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│ vi: set et ft=c ts=8 sts=2 sw=2 fenc=utf-8 :vi │ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ ╞══════════════════════════════════════════════════════════════════════════════╡ │ Copyright 2020 Justine Alexandra Roberts Tunney │ │ │ @@ -16,18 +16,17 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/dll.h" #include "libc/stdio/internal.h" #include "libc/sysv/consts/fileno.h" #include "libc/sysv/consts/o.h" -#include "libc/thread/thread.h" static FILE __stderr = { .fd = STDERR_FILENO, .bufmode = _IONBF, - .iomode = O_WRONLY, - .buf = __stderr.mem, - .size = sizeof(stderr->mem), + .oflags = O_WRONLY, .lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP, + .elem = {&__stderr.elem, &__stderr.elem}, }; /** @@ -35,6 +34,6 @@ static FILE __stderr = { */ FILE *stderr = &__stderr; -__attribute__((__constructor__(60))) static textstartup void errinit(void) { - __fflush_register(stderr); +__attribute__((__constructor__(60))) static textstartup void stderr_init(void) { + dll_make_last(&__stdio.files, &__stderr.elem); } diff --git a/libc/stdio/stdin.c b/libc/stdio/stdin.c index c5c3f6c2e..8b1b44b9d 100644 --- a/libc/stdio/stdin.c +++ b/libc/stdio/stdin.c @@ -1,5 +1,5 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│ vi: set et ft=c ts=8 sts=2 sw=2 fenc=utf-8 :vi │ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ ╞══════════════════════════════════════════════════════════════════════════════╡ │ Copyright 2020 Justine Alexandra Roberts Tunney │ │ │ @@ -17,19 +17,25 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/struct/stat.h" +#include "libc/intrin/dll.h" #include "libc/stdio/internal.h" #include "libc/sysv/consts/fileno.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/s.h" #include "libc/thread/thread.h" +__static_yoink("fflush"); + +static char __stdin_buf[BUFSIZ]; + static FILE __stdin = { .fd = STDIN_FILENO, - .iomode = O_RDONLY, + .oflags = O_RDONLY, .bufmode = _IOFBF, - .buf = __stdin.mem, - .size = sizeof(stdin->mem), + .buf = __stdin_buf, + .size = sizeof(__stdin_buf), .lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP, + .elem = {&__stdin.elem, &__stdin.elem}, }; /** @@ -37,9 +43,9 @@ static FILE __stdin = { */ FILE *stdin = &__stdin; -__attribute__((__constructor__(60))) static textstartup void initin(void) { +__attribute__((__constructor__(60))) static textstartup void stdin_init(void) { struct stat st; - if (fstat(STDIN_FILENO, &st) || !S_ISREG(st.st_mode)) + if (fstat(STDIN_FILENO, &st) || S_ISCHR(st.st_mode)) stdin->bufmode = _IONBF; - __fflush_register(stdin); + dll_make_last(&__stdio.files, &__stdin.elem); } diff --git a/libc/stdio/stdout.c b/libc/stdio/stdout.c index 4c6b9b2d6..86a34f9f3 100644 --- a/libc/stdio/stdout.c +++ b/libc/stdio/stdout.c @@ -1,5 +1,5 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│ vi: set et ft=c ts=8 sts=2 sw=2 fenc=utf-8 :vi │ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ ╞══════════════════════════════════════════════════════════════════════════════╡ │ Copyright 2020 Justine Alexandra Roberts Tunney │ │ │ @@ -16,17 +16,22 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/dll.h" #include "libc/stdio/internal.h" #include "libc/sysv/consts/fileno.h" #include "libc/sysv/consts/o.h" -#include "libc/thread/thread.h" + +__static_yoink("fflush"); + +static char __stdout_buf[BUFSIZ]; static FILE __stdout = { .fd = STDOUT_FILENO, - .iomode = O_WRONLY, - .buf = __stdout.mem, - .size = sizeof(stdout->mem), + .oflags = O_WRONLY, + .buf = __stdout_buf, + .size = sizeof(__stdout_buf), .lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP, + .elem = {&__stdout.elem, &__stdout.elem}, // Unlike other C libraries we don't bother calling fstat() to check // if stdio is a character device and we instead choose to always @@ -42,6 +47,6 @@ static FILE __stdout = { */ FILE *stdout = &__stdout; -__attribute__((__constructor__(60))) static textstartup void outinit(void) { - __fflush_register(stdout); +__attribute__((__constructor__(60))) static textstartup void stdout_init(void) { + dll_make_last(&__stdio.files, &__stdout.elem); } diff --git a/libc/stdio/strfry.c b/libc/stdio/strfry.c index eac05107d..56a703cbd 100644 --- a/libc/stdio/strfry.c +++ b/libc/stdio/strfry.c @@ -16,14 +16,23 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/mem/shuffle.internal.h" #include "libc/stdio/rand.h" #include "libc/str/str.h" /** - * Jumbles up string. + * Performs Fisher-Yates shuffle on string in-place to create anagram. + * + * This implementation uses rand() so `srand(time(0))` may be desired. */ char *strfry(char *s) { - shuffle(rand, s, strlen(s)); + size_t i = strlen(s); + while (i > 1) { + size_t x = rand(); + size_t y = rand(); + size_t j = ((x << 31) ^ y) % i--; + char t = s[j]; + s[j] = s[i]; + s[i] = t; + } return s; } diff --git a/libc/stdio/vfprintf_unlocked.c b/libc/stdio/vfprintf_unlocked.c index d65bfc089..d0285dcbd 100644 --- a/libc/stdio/vfprintf_unlocked.c +++ b/libc/stdio/vfprintf_unlocked.c @@ -19,6 +19,7 @@ #include "libc/assert.h" #include "libc/calls/calls.h" #include "libc/fmt/internal.h" +#include "libc/intrin/kprintf.h" #include "libc/stdckdint.h" #include "libc/stdio/internal.h" #include "libc/stdio/stdio.h" @@ -46,9 +47,8 @@ static int __vfprintf_flbuf(const char *s, struct state *t, size_t n) { } else { rc = -1; } - if (ckd_add(&t->n, t->n, n)) { + if (ckd_add(&t->n, t->n, n)) rc = eoverflow(); - } } else { rc = 0; } @@ -60,9 +60,8 @@ static int __vfprintf_nbuf(const char *s, struct state *t, size_t n) { for (i = 0; i < n; ++i) { t->b.p[t->b.n++] = s[i]; if (t->b.n == sizeof(t->b.p)) { - if (!fwrite_unlocked(t->b.p, 1, t->b.n, t->f)) { + if (!fwrite_unlocked(t->b.p, 1, t->b.n, t->f)) return -1; - } t->b.n = 0; } else if (ckd_add(&t->n, t->n, 1)) { return eoverflow(); @@ -91,9 +90,7 @@ int vfprintf_unlocked(FILE *f, const char *fmt, va_list va) { if (!st.b.n) { rc = st.n; } else if (fwrite_unlocked(st.b.p, 1, st.b.n, st.f)) { - if (ckd_add(&rc, st.n, st.b.n)) { - rc = eoverflow(); - } + rc = st.n; } else { rc = -1; } diff --git a/libc/str/BUILD.mk b/libc/str/BUILD.mk index 5e10f4ace..b0b2a163a 100644 --- a/libc/str/BUILD.mk +++ b/libc/str/BUILD.mk @@ -77,13 +77,7 @@ o/$(MODE)/libc/str/iswseparator.o: private \ # ensure that division is optimized o/$(MODE)/libc/str/bcmp.o \ -o/$(MODE)/libc/str/strcmp.o \ -o/$(MODE)/libc/str/windowsdurationtotimeval.o \ -o/$(MODE)/libc/str/windowsdurationtotimespec.o \ -o/$(MODE)/libc/str/timevaltowindowstime.o \ -o/$(MODE)/libc/str/timespectowindowstime.o \ -o/$(MODE)/libc/str/windowstimetotimeval.o \ -o/$(MODE)/libc/str/windowstimetotimespec.o: private \ +o/$(MODE)/libc/str/strcmp.o: private \ CFLAGS += \ -O2 diff --git a/libc/str/memccpy.c b/libc/str/memccpy.c index e910f0929..2d42920a6 100644 --- a/libc/str/memccpy.c +++ b/libc/str/memccpy.c @@ -45,13 +45,14 @@ * @asyncsignalsafe */ void *memccpy(void *dst, const void *src, int c, size_t n) { - char *d; - size_t i; - const char *s; - for (d = dst, s = src, i = 0; i < n; ++i) { - if (((d[i] = s[i]) & 255) == (c & 255)) { - return d + i + 1; - } + const char *p; + // this memchr() call is only correct if your memchr() implementation + // offers the same readahead safety guarantees as cosmopolitan's does + if ((p = memchr(src, c, n))) { + size_t m = p + 1 - (const char *)src; + memmove(dst, src, m); + return (char *)dst + m; } + memmove(dst, src, n); return 0; } diff --git a/libc/str/strcasestr.c b/libc/str/strcasestr.c index ec40c16f1..cf0cfe2d5 100644 --- a/libc/str/strcasestr.c +++ b/libc/str/strcasestr.c @@ -17,10 +17,69 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/str/str.h" -#include "libc/dce.h" +#include "libc/ctype.h" +#include "libc/mem/alloca.h" +#include "libc/runtime/stack.h" #include "libc/str/tab.h" +#include "third_party/aarch64/arm_neon.internal.h" +#include "third_party/intel/immintrin.internal.h" -typedef char xmm_t __attribute__((__vector_size__(16), __aligned__(16))); +static int ToUpper(int c) { + return 'a' <= c && c <= 'z' ? c - ('a' - 'A') : c; +} + +static void computeLPS(const char *pattern, long M, long *lps) { + long len = 0; + lps[0] = 0; + long i = 1; + while (i < M) { + if (kToLower[pattern[i] & 255] == kToLower[pattern[len] & 255]) { + len++; + lps[i] = len; + i++; + } else { + if (len != 0) { + len = lps[len - 1]; + } else { + lps[i] = 0; + i++; + } + } + } +} + +static char *kmp(const char *s, size_t n, const char *ss, size_t m) { + if (!m) + return (char *)s; + if (n < m) + return NULL; +#pragma GCC push_options +#pragma GCC diagnostic ignored "-Walloca-larger-than=" +#pragma GCC diagnostic ignored "-Wanalyzer-out-of-bounds" + long need = sizeof(long) * m; + long *lps = (long *)alloca(need); + CheckLargeStackAllocation(lps, need); +#pragma GCC pop_options + computeLPS(ss, m, lps); + long i = 0; + long j = 0; + while (i < n) { + if (kToLower[ss[j] & 255] == kToLower[s[i] & 255]) { + i++; + j++; + } + if (j == m) { + return (char *)(s + i - j); + } else if (i < n && kToLower[ss[j] & 255] != kToLower[s[i] & 255]) { + if (j != 0) { + j = lps[j - 1]; + } else { + i++; + } + } + } + return NULL; +} /** * Searches for substring case-insensitively. @@ -28,65 +87,108 @@ typedef char xmm_t __attribute__((__vector_size__(16), __aligned__(16))); * @param haystack is the search area, as a NUL-terminated string * @param needle is the desired substring, also NUL-terminated * @return pointer to first substring within haystack, or NULL - * @note this implementation goes fast in practice but isn't hardened - * against pathological cases, and therefore shouldn't be used on - * untrustworthy data * @asyncsignalsafe * @see strstr() */ -__vex char *strcasestr(const char *haystack, const char *needle) { -#if defined(__x86_64__) && !defined(__chibicc__) - char c; - size_t i; - unsigned k, m; - const xmm_t *p; - xmm_t v, n1, n2, z = {0}; +char *strcasestr(const char *haystack, const char *needle) { if (haystack == needle || !*needle) return (char *)haystack; - c = *needle; - n1 = (xmm_t){c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c}; - c = kToLower[c & 255]; - n2 = (xmm_t){c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c}; +#if defined(__x86_64__) && !defined(__chibicc__) + size_t i; + unsigned k, m; + const __m128i *p; + long progress = 0; + __m128i v, nl, nu, z = _mm_setzero_si128(); + const char *hay = haystack; + char first_lower = kToLower[*needle & 255]; + char first_upper = ToUpper(*needle); + nl = _mm_set1_epi8(first_lower); + nu = _mm_set1_epi8(first_upper); for (;;) { - k = (uintptr_t)haystack & 15; - p = (const xmm_t *)((uintptr_t)haystack & -16); - v = *p; - m = __builtin_ia32_pmovmskb128((v == z) | (v == n1) | (v == n2)); + k = (uintptr_t)hay & 15; + p = (const __m128i *)((uintptr_t)hay & -16); + v = _mm_load_si128(p); + m = _mm_movemask_epi8(_mm_or_si128( + _mm_or_si128(_mm_cmpeq_epi8(v, z), // Check for null terminator + _mm_cmpeq_epi8(v, nl)), // Check lowercase + _mm_cmpeq_epi8(v, nu))); // Check uppercase m >>= k; m <<= k; while (!m) { - v = *++p; - m = __builtin_ia32_pmovmskb128((v == z) | (v == n1) | (v == n2)); + progress += 16; + v = _mm_load_si128(++p); + m = _mm_movemask_epi8(_mm_or_si128( + _mm_or_si128(_mm_cmpeq_epi8(v, z), _mm_cmpeq_epi8(v, nl)), + _mm_cmpeq_epi8(v, nu))); } - haystack = (const char *)p + __builtin_ctzl(m); + int offset = __builtin_ctzl(m); + progress += offset; + hay = (const char *)p + offset; for (i = 0;; ++i) { + if (--progress <= -512) + goto OfferPathologicalAssurances; if (!needle[i]) - return (/*unconst*/ char *)haystack; - if (!haystack[i]) + return (char *)hay; + if (!hay[i]) break; - if (kToLower[needle[i] & 255] != kToLower[haystack[i] & 255]) + if (kToLower[needle[i] & 255] != kToLower[hay[i] & 255]) break; } - if (!*haystack++) + if (!*hay++) break; } return 0; -#else +#elif defined(__aarch64__) && defined(__ARM_NEON) size_t i; - if (haystack == needle || !*needle) - return (void *)haystack; + const char *hay = haystack; + uint8_t first_lower = kToLower[*needle & 255]; + uint8_t first_upper = ToUpper(*needle); + uint8x16_t nl = vdupq_n_u8(first_lower); + uint8x16_t nu = vdupq_n_u8(first_upper); + uint8x16_t z = vdupq_n_u8(0); + long progress = 0; for (;;) { + int k = (uintptr_t)hay & 15; + hay = (const char *)((uintptr_t)hay & -16); + uint8x16_t v = vld1q_u8((const uint8_t *)hay); + uint8x16_t cmp_lower = vceqq_u8(v, nl); + uint8x16_t cmp_upper = vceqq_u8(v, nu); + uint8x16_t cmp_null = vceqq_u8(v, z); + uint8x16_t cmp = vorrq_u8(vorrq_u8(cmp_lower, cmp_upper), cmp_null); + uint8x8_t mask = vshrn_n_u16(vreinterpretq_u16_u8(cmp), 4); + uint64_t m; + vst1_u8((uint8_t *)&m, mask); + m >>= k * 4; + m <<= k * 4; + while (!m) { + hay += 16; + progress += 16; + v = vld1q_u8((const uint8_t *)hay); + cmp_lower = vceqq_u8(v, nl); + cmp_upper = vceqq_u8(v, nu); + cmp_null = vceqq_u8(v, z); + cmp = vorrq_u8(vorrq_u8(cmp_lower, cmp_upper), cmp_null); + mask = vshrn_n_u16(vreinterpretq_u16_u8(cmp), 4); + vst1_u8((uint8_t *)&m, mask); + } + int offset = __builtin_ctzll(m) >> 2; + progress += offset; + hay += offset; for (i = 0;; ++i) { + if (--progress <= -512) + goto OfferPathologicalAssurances; if (!needle[i]) - return (/*unconst*/ char *)haystack; - if (!haystack[i]) + return (char *)hay; + if (!hay[i]) break; - if (kToLower[needle[i] & 255] != kToLower[haystack[i] & 255]) + if (kToLower[needle[i] & 255] != kToLower[hay[i] & 255]) break; } - if (!*haystack++) + if (!*hay++) break; } return 0; #endif +OfferPathologicalAssurances: + return kmp(haystack, strlen(haystack), needle, strlen(needle)); } diff --git a/libc/system/BUILD.mk b/libc/system/BUILD.mk new file mode 100644 index 000000000..00d4aaae9 --- /dev/null +++ b/libc/system/BUILD.mk @@ -0,0 +1,83 @@ +#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐ +#── vi: set noet ft=make ts=8 sw=8 fenc=utf-8 :vi ────────────────────┘ + +PKGS += LIBC_SYSTEM + +LIBC_SYSTEM_ARTIFACTS += LIBC_SYSTEM_A +LIBC_SYSTEM = $(LIBC_SYSTEM_A_DEPS) $(LIBC_SYSTEM_A) +LIBC_SYSTEM_A = o/$(MODE)/libc/system/system.a +LIBC_SYSTEM_A_FILES := $(wildcard libc/system/*) +LIBC_SYSTEM_A_HDRS = $(filter %.h,$(LIBC_SYSTEM_A_FILES)) +LIBC_SYSTEM_A_INCS = $(filter %.inc,$(LIBC_SYSTEM_A_FILES)) +LIBC_SYSTEM_A_SRCS_S = $(filter %.S,$(LIBC_SYSTEM_A_FILES)) +LIBC_SYSTEM_A_SRCS_C = $(filter %.c,$(LIBC_SYSTEM_A_FILES)) + +LIBC_SYSTEM_A_SRCS = \ + $(LIBC_SYSTEM_A_SRCS_S) \ + $(LIBC_SYSTEM_A_SRCS_C) + +LIBC_SYSTEM_A_OBJS = \ + $(LIBC_SYSTEM_A_SRCS_S:%.S=o/$(MODE)/%.o) \ + $(LIBC_SYSTEM_A_SRCS_C:%.c=o/$(MODE)/%.o) + +LIBC_SYSTEM_A_CHECKS = \ + $(LIBC_SYSTEM_A).pkg \ + $(LIBC_SYSTEM_A_HDRS:%=o/$(MODE)/%.ok) + +LIBC_SYSTEM_A_DIRECTDEPS = \ + LIBC_CALLS \ + LIBC_FMT \ + LIBC_INTRIN \ + LIBC_NEXGEN32E \ + LIBC_PROC \ + LIBC_RUNTIME \ + LIBC_STDIO \ + LIBC_STR \ + LIBC_SYSV \ + THIRD_PARTY_MUSL \ + THIRD_PARTY_SED \ + THIRD_PARTY_TR \ + +LIBC_SYSTEM_A_DEPS := \ + $(call uniq,$(foreach x,$(LIBC_SYSTEM_A_DIRECTDEPS),$($(x)))) + +$(LIBC_SYSTEM_A):libc/system/ \ + $(LIBC_SYSTEM_A).pkg \ + $(LIBC_SYSTEM_A_OBJS) + +$(LIBC_SYSTEM_A).pkg: \ + $(LIBC_SYSTEM_A_OBJS) \ + $(foreach x,$(LIBC_SYSTEM_A_DIRECTDEPS),$($(x)_A).pkg) + +# offer assurances about the stack safety of cosmo libc +$(LIBC_SYSTEM_A_OBJS): private COPTS += -Wframe-larger-than=4096 -Walloca-larger-than=4096 + +$(LIBC_SYSTEM_A_OBJS): private \ + CFLAGS += \ + -fno-sanitize=all \ + -Wframe-larger-than=4096 \ + -Walloca-larger-than=4096 + +o/$(MODE)/libc/system/fputc.o: private \ + CFLAGS += \ + -O3 + +o//libc/system/appendw.o: private \ + CFLAGS += \ + -Os + +o/$(MODE)/libc/system/dirstream.o \ +o/$(MODE)/libc/system/mt19937.o: private \ + CFLAGS += \ + -ffunction-sections + +LIBC_SYSTEM_LIBS = $(foreach x,$(LIBC_SYSTEM_ARTIFACTS),$($(x))) +LIBC_SYSTEM_SRCS = $(foreach x,$(LIBC_SYSTEM_ARTIFACTS),$($(x)_SRCS)) +LIBC_SYSTEM_HDRS = $(foreach x,$(LIBC_SYSTEM_ARTIFACTS),$($(x)_HDRS)) +LIBC_SYSTEM_INCS = $(foreach x,$(LIBC_SYSTEM_ARTIFACTS),$($(x)_INCS)) +LIBC_SYSTEM_CHECKS = $(foreach x,$(LIBC_SYSTEM_ARTIFACTS),$($(x)_CHECKS)) +LIBC_SYSTEM_OBJS = $(foreach x,$(LIBC_SYSTEM_ARTIFACTS),$($(x)_OBJS)) +$(LIBC_SYSTEM_OBJS): $(BUILD_FILES) libc/system/BUILD.mk + +.PHONY: o/$(MODE)/libc/system +o/$(MODE)/libc/system: $(LIBC_SYSTEM_CHECKS) diff --git a/libc/proc/cocmd.c b/libc/system/cocmd.c similarity index 96% rename from libc/proc/cocmd.c rename to libc/system/cocmd.c index 5345b13b8..36bbbf5ba 100644 --- a/libc/proc/cocmd.c +++ b/libc/system/cocmd.c @@ -743,15 +743,12 @@ static int TryBuiltin(bool wantexec) { return Usleep(); if (!strcmp(args[0], "toupper")) return Toupper(); - if (_weaken(_tr) && !strcmp(args[0], "tr")) { - return Fake(_weaken(_tr), wantexec); - } - if (_weaken(_sed) && !strcmp(args[0], "sed")) { - return Fake(_weaken(_sed), wantexec); - } - if (_weaken(_awk) && !strcmp(args[0], "awk")) { + if (!strcmp(args[0], "tr")) + return Fake(_tr, wantexec); + if (!strcmp(args[0], "sed")) + return Fake(_sed, wantexec); + if (_weaken(_awk) && strcmp(args[0], "awk")) return Fake(_weaken(_awk), wantexec); - } if (_weaken(_curl) && !strcmp(args[0], "curl")) { return Fake(_weaken(_curl), wantexec); } @@ -1055,12 +1052,9 @@ int _cocmd(int argc, char **argv, char **envp) { unsupported['('] = true; unsupported[')'] = true; unsupported['{'] = true; - unsupported['}'] = false; // Perl t/op/exec.t depends on unpaired } being - // passed from the shell to Perl - if (!_weaken(glob)) { - unsupported['*'] = true; - unsupported['?'] = true; - } + // Perl t/op/exec.t depends on unpaired } being + // passed from the shell to Perl + unsupported['}'] = false; if (argc >= 3 && !strcmp(argv[1], "--")) { for (i = 2; i < argc; ++i) { @@ -1121,18 +1115,16 @@ int _cocmd(int argc, char **argv, char **envp) { Open(GetRedirectArg(prog, arg, 1), 0, O_RDONLY); } else { int globrc = GLOB_NOMATCH; - if (_weaken(glob)) { - globrc = _weaken(glob)(arg, globFlags, NULL, &globTheBuilder); - if (globrc == 0) { - for (; globCount < globTheBuilder.gl_pathc; globCount++) { - args[n++] = globTheBuilder.gl_pathv[globCount]; - } - } else if (globrc != GLOB_NOMATCH) { - tinyprint(2, prog, ": error: with glob\n", NULL); - _Exit(16); + globrc = glob(arg, globFlags, NULL, &globTheBuilder); + if (globrc == 0) { + for (; globCount < globTheBuilder.gl_pathc; globCount++) { + args[n++] = globTheBuilder.gl_pathv[globCount]; } - globFlags |= GLOB_APPEND; + } else if (globrc != GLOB_NOMATCH) { + tinyprint(2, prog, ": error: with glob\n", NULL); + _Exit(16); } + globFlags |= GLOB_APPEND; if (globrc == GLOB_NOMATCH) { args[n++] = arg; } diff --git a/libc/stdio/fleaks.c b/libc/system/fleaks.c similarity index 100% rename from libc/stdio/fleaks.c rename to libc/system/fleaks.c diff --git a/libc/stdio/pclose.c b/libc/system/pclose.c similarity index 100% rename from libc/stdio/pclose.c rename to libc/system/pclose.c diff --git a/libc/stdio/popen.c b/libc/system/popen.c similarity index 95% rename from libc/stdio/popen.c rename to libc/system/popen.c index a7489d261..2636cc5ff 100644 --- a/libc/stdio/popen.c +++ b/libc/system/popen.c @@ -22,7 +22,6 @@ #include "libc/intrin/weaken.h" #include "libc/paths.h" #include "libc/runtime/runtime.h" -#include "libc/stdio/fflush.internal.h" #include "libc/stdio/internal.h" #include "libc/stdio/stdio.h" #include "libc/sysv/consts/f.h" @@ -54,7 +53,7 @@ * @cancelationpoint */ FILE *popen(const char *cmdline, const char *mode) { - FILE *f, *f2; + FILE *f; int e, rc, pid, dir, flags, pipefds[2]; flags = fopenflags(mode); if ((flags & O_ACCMODE) == O_RDONLY) { @@ -84,14 +83,19 @@ FILE *popen(const char *cmdline, const char *mode) { unassert(!close(pipefds[0])); if (pipefds[1] != !dir) unassert(!close(pipefds[1])); + // "The popen() function shall ensure that any streams from // previous popen() calls that remain open in the parent // process are closed in the new child process." -POSIX - for (int i = 0; i < __fflush.handles.i; ++i) { - if ((f2 = __fflush.handles.p[i]) && f2->pid) { + for (struct Dll *e = dll_first(__stdio.files); e; + e = dll_next(__stdio.files, e)) { + FILE *f2 = FILE_CONTAINER(e); + if (f != f2 && f2->pid && f2->fd != -1) { close(f2->fd); + f2->fd = -1; } } + _Exit(_cocmd(3, (char *[]){ "popen", diff --git a/libc/proc/system.c b/libc/system/system.c similarity index 96% rename from libc/proc/system.c rename to libc/system/system.c index fddb4a0dd..6755d1f06 100644 --- a/libc/proc/system.c +++ b/libc/system/system.c @@ -38,9 +38,7 @@ * provides Bourne-like syntax on all platforms, including Windows. Many * builtin commands are included, e.g. exit, cd, rm, [, cat, wait, exec, * env, echo, read, true, test, kill, touch, rmdir, mkdir, false, mktemp - * and usleep. It's also possible to __static_yoink() the symbols `_tr`, - * `_sed`, `_awk`, and `_curl` for the tr, sed, awk and curl commands if - * you're using the Cosmopolitan mono-repo. + * sed, tr, and usleep. * * If you just have a program name and arguments, and you don't need the * full power of a UNIX-like shell, then consider using the Cosmopolitan diff --git a/libc/proc/systemvpe.c b/libc/system/systemvpe.c similarity index 97% rename from libc/proc/systemvpe.c rename to libc/system/systemvpe.c index e44ed8d66..1165d45c3 100644 --- a/libc/proc/systemvpe.c +++ b/libc/system/systemvpe.c @@ -52,9 +52,8 @@ int systemvpe(const char *prog, char *const argv[], char *const envp[]) { int pid, wstatus; char pathbuf[PATH_MAX + 1]; sigset_t chldmask, savemask; - if (!(exe = commandv(prog, pathbuf, sizeof(pathbuf)))) { + if (!(exe = commandv(prog, pathbuf, sizeof(pathbuf)))) return -1; - } sigemptyset(&chldmask); sigaddset(&chldmask, SIGINT); sigaddset(&chldmask, SIGQUIT); @@ -62,7 +61,7 @@ int systemvpe(const char *prog, char *const argv[], char *const envp[]) { sigprocmask(SIG_BLOCK, &chldmask, &savemask); if (!(pid = vfork())) { sigprocmask(SIG_SETMASK, &savemask, 0); - execve(prog, argv, envp); + execve(exe, argv, envp); _Exit(127); } else if (pid == -1) { wstatus = -1; diff --git a/libc/sysv/BUILD.mk b/libc/sysv/BUILD.mk index f9bd91985..19f328784 100644 --- a/libc/sysv/BUILD.mk +++ b/libc/sysv/BUILD.mk @@ -84,7 +84,8 @@ o/$(MODE)/libc/sysv/sysret.o: private \ CFLAGS += \ -ffreestanding \ -fno-stack-protector \ - -fno-sanitize=all + -fno-sanitize=all \ + -mgeneral-regs-only ifeq ($(ARCH),aarch64) o/$(MODE)/libc/sysv/sysv.o: private \ diff --git a/libc/sysv/consts.sh b/libc/sysv/consts.sh index a372572b5..353bfc464 100755 --- a/libc/sysv/consts.sh +++ b/libc/sysv/consts.sh @@ -227,7 +227,6 @@ syscon mmap MAP_LOCKED 0x00002000 0x00002000 0 0 0 0 0 0 syscon mmap MAP_NORESERVE 0x00004000 0x00004000 0x00000040 0x00000040 0 0 0x00000040 0 # Linux calls it "reserve"; NT calls it "commit"? which is default? syscon mmap MAP_POPULATE 0x00008000 0x00008000 0 0 0x00040000 0 0 0 # MAP_PREFAULT_READ on FreeBSD; can avoid madvise(MADV_WILLNEED) on private file mapping syscon mmap MAP_NONBLOCK 0x00010000 0x00010000 0 0 0 0 0 0 -syscon mmap MAP_NOFORK 0 0 0 0 0 0 0 0x10000000 # used on pages internal to our mmap() implemention on windows syscon mmap MAP_SYNC 0x00080000 0x00080000 0 0 0 0 0 0 # perform synchronous page faults for mapping (Linux 4.15+) syscon mmap MAP_HUGETLB 0x00040000 -1 -1 -1 -1 -1 -1 -1 # make it inherit across execve() syscon mmap MAP_INHERIT -1 -1 -1 -1 -1 -1 0x00000080 -1 # make it inherit across execve() @@ -1104,8 +1103,8 @@ syscon limits _ARG_MAX 128*1024 128*1024 1024*1024 1024*1024 512*1024 51 syscon limits _NAME_MAX 255 255 255 255 255 255 511 255 # probably higher on windows? syscon limits _PATH_MAX 4096 4096 1024 1024 1024 1024 1024 260 # syscon limits _NSIG 64 64 32 32 128 32 64 64 # _SIG_MAXSIG on FreeBSD -syscon limits _MINSIGSTKSZ 2048 2048 32768 32768 4096 12288 8192 2048 # -syscon limits _SIGSTKSZ 8192 2048 131072 131072 36864 28672 40960 8192 # +syscon limits _MINSIGSTKSZ 2048 6144 8192 32768 6656 14336 8192 2048 # FreeBSD upscaled a bit for ARM +syscon limits _SIGSTKSZ 10240 10240 131072 131072 36864 28672 40960 10240 # # unmount() flags # a.k.a. umount2() on linux @@ -1138,32 +1137,12 @@ syscon reboot RB_NOSYNC 0x20000000 0x20000000 4 4 4 4 4 0x2000000 syscon msg MSG_OOB 1 1 1 1 1 1 1 1 # consensus syscon msg MSG_PEEK 2 2 2 2 2 2 2 2 # consensus syscon msg MSG_DONTROUTE 4 4 4 4 4 4 4 4 # consensus -syscon msg MSG_FASTOPEN 0x20000000 0x20000000 0 0 0 0 0 0 # TODO -syscon msg MSG_WAITALL 0x0100 0x0100 0x40 0x40 0x40 0x40 0x40 8 # bsd consensus -syscon msg MSG_MORE 0x8000 0x8000 0 0 0 0 0 0 # send/sendto: manual TCP_CORK hbasically -syscon msg MSG_NOSIGNAL 0x4000 0x4000 0x80000 0x80000 0x020000 0x0400 0x0400 0 # send/sendto: don't SIGPIPE on EOF syscon msg MSG_DONTWAIT 0x40 0x40 0x80 0x80 0x80 0x80 0x80 0x40 # send/sendto: manual non-blocking +syscon msg MSG_WAITALL 0x0100 0x0100 0x40 0x40 0x40 0x40 0x40 8 # bsd consensus +syscon msg MSG_NOSIGNAL 0x4000 0x4000 0x80000 0x80000 0x020000 0x0400 0x0400 0x10000000 # send/sendto: don't raise sigpipe on local shutdown syscon msg MSG_TRUNC 0x20 0x20 0x10 0x10 0x10 0x10 0x10 0x0100 # bsd consensus syscon msg MSG_CTRUNC 8 8 0x20 0x20 0x20 0x20 0x20 0x0200 # bsd consensus -syscon msg MSG_ERRQUEUE 0x2000 0x2000 0 0 0 0 0 0x1000 # bsd consensus -syscon msg MSG_NOERROR 0x1000 0x1000 0x1000 0x1000 0x1000 0x1000 0x1000 0 # unix consensus -syscon msg MSG_EOR 0x80 0x80 8 8 8 8 8 0 # bsd consensus -syscon msg MSG_CMSG_CLOEXEC 0x40000000 0x40000000 0 0 0x040000 0x0800 0x0800 0 -syscon msg MSG_WAITFORONE 0x010000 0x010000 0 0 0x080000 0 0x2000 0 -syscon msg MSG_BATCH 0x040000 0x040000 0 0 0 0 0 0 -syscon msg MSG_CONFIRM 0x0800 0x0800 0 0 0 0 0 0 -syscon msg MSG_EXCEPT 0x2000 0x2000 0 0 0 0 0 0 -syscon msg MSG_FIN 0x0200 0x0200 0x0100 0x0100 0x0100 0 0 0 -syscon msg MSG_EOF 0x0200 0x0200 0x0100 0x0100 0x0100 0 0 0 -syscon msg MSG_INFO 12 12 0 0 0 0 0 0 -syscon msg MSG_PARITY_ERROR 9 9 0 0 0 0 0 0 -syscon msg MSG_PROXY 0x10 0x10 0 0 0 0 0 0 -syscon msg MSG_RST 0x1000 0x1000 0 0 0 0 0 0 -syscon msg MSG_STAT 11 11 0 0 0 0 0 0 -syscon msg MSG_SYN 0x0400 0x0400 0 0 0 0 0 0 -syscon msg MSG_BCAST 0 0 0 0 0 0x100 0x100 0 -syscon msg MSG_MCAST 0 0 0 0 0 0x200 0x200 0 -syscon msg MSG_NOTIFICATION 0x8000 0x8000 0 0 0x2000 0 0x4000 0 +syscon msg MSG_FASTOPEN 0x20000000 0x20000000 -1 -1 -1 -1 -1 -1 # # getpriority() / setpriority() magnums (a.k.a. nice) # @@ -1435,16 +1414,6 @@ syscon shm SHM_NORESERVE 0x1000 0x1000 0 0 0 0 0 0 syscon shm SHM_REMAP 0x4000 0x4000 0 0 0 0 0 0 syscon shm SHM_ANON 0 0 0 0 1 0 0 0 -syscon lock LOCK_UNLOCK_CACHE 54 54 0 0 0 0 0 0 # wut - -syscon misc IP6F_MORE_FRAG 0x0100 0x0100 0x0100 0x0100 0x0100 0x0100 0x0100 0x0100 # consensus -syscon misc IP6F_OFF_MASK 0xf8ff 0xf8ff 0xf8ff 0xf8ff 0xf8ff 0xf8ff 0xf8ff 0xf8ff # consensus -syscon misc IP6F_RESERVED_MASK 0x0600 0x0600 0x0600 0x0600 0x0600 0x0600 0x0600 0x0600 # consensus - -syscon misc L_SET 0 0 0 0 0 0 0 0 # consensus -syscon misc L_INCR 1 1 1 1 1 1 1 0 # unix consensus -syscon misc L_XTND 2 2 2 2 2 2 2 0 # unix consensus - syscon misc SHUT_RD 0 0 0 0 0 0 0 0 # consensus (SD_RECEIVE) syscon misc SHUT_WR 1 1 1 1 1 1 1 1 # consensus (SD_SEND) syscon misc SHUT_RDWR 2 2 2 2 2 2 2 2 # consensus (SD_BOTH) @@ -1490,11 +1459,6 @@ syscon misc ACCT_COMM 0x10 0x10 0 0 0 0 0 0 syscon misc COMMAND_COMPLETE 0 0 0 0 0 0 0 0 # consensus syscon misc COMMAND_TERMINATED 17 17 0 0 0 0 0 0 -syscon select FD_SETSIZE 0x0400 0x0400 0x0400 0x0400 0x0400 0x0400 0x0400 0x0400 # forced consensus (0x40 on NT) - -syscon misc MATH_ERREXCEPT 2 2 2 2 2 2 2 0 # unix consensus -syscon misc MATH_ERRNO 1 1 1 1 1 1 1 0 # unix consensus - syscon misc MCAST_BLOCK_SOURCE 43 43 84 84 84 0 0 43 syscon misc MCAST_JOIN_GROUP 42 42 80 80 80 0 0 41 syscon misc MCAST_JOIN_SOURCE_GROUP 46 46 82 82 82 0 0 45 @@ -1963,86 +1927,8 @@ syscon junkerr EKEYREJECTED 129 129 -1 -1 -1 -1 -1 -1 syscon junkerr ERFKILL 132 132 -1 -1 -1 -1 -1 -1 syscon junkerr EHWPOISON 133 133 -1 -1 -1 -1 -1 -1 -syscon misc FIFOTYPE 54 54 54 54 54 54 54 0 # unix consensus -syscon misc GRPQUOTA 1 1 1 1 1 1 1 0 # unix consensus -syscon misc IF_NAMESIZE 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0 # unix consensus -syscon misc INTERMEDIATE_C_GOOD 10 10 0 0 0 0 0 0 -syscon misc INTERMEDIATE_GOOD 8 8 0 0 0 0 0 0 - +syscon select FD_SETSIZE 0x0400 0x0400 0x0400 0x0400 0x0400 0x0400 0x0400 0x0400 # forced consensus (0x40 on NT) syscon misc IOV_MAX 1024 1024 1024 1024 1024 1024 1024 16 # unix consensus & MSG_MAXIOVLEN -syscon misc LINE_MAX 0x0800 0x0800 0x0800 0x0800 0x0800 0x0800 0x0800 0 # unix consensus -syscon misc LINKED_CMD_COMPLETE 10 10 0 0 0 0 0 0 -syscon misc LINKED_FLG_CMD_COMPLETE 11 11 0 0 0 0 0 0 - -syscon misc LNKTYPE 49 49 49 49 49 49 49 0 # unix consensus -syscon misc MAXNAMLEN 255 255 255 255 255 255 255 0 # unix consensus -syscon misc MAXQUOTAS 2 2 2 2 2 2 2 0 # unix consensus -syscon misc MEDIUM_ERROR 3 3 0 0 0 0 0 0 -syscon misc MEDIUM_SCAN 56 56 0 0 0 0 0 0 - -syscon misc NBBY 8 8 8 8 8 8 8 0 # unix consensus -syscon misc NR_DQHASH 43 43 0 0 0 0 0 0 -syscon misc NR_DQUOTS 0x0100 0x0100 0 0 0 0 0 0 - -syscon misc PERSISTENT_RESERVE_IN 94 94 0 0 0 0 0 0 -syscon misc PERSISTENT_RESERVE_OUT 95 95 0 0 0 0 0 0 - -syscon misc PRELIM 1 1 1 1 1 1 1 0 # unix consensus -syscon misc REGTYPE 48 48 48 48 48 48 48 0 # unix consensus -syscon misc RES_PRF_CLASS 4 4 4 4 4 4 4 0 # unix consensus -syscon misc RHF_GUARANTEE_START_INIT 0x80 0x80 0 0 0 0 0 0 -syscon misc RHF_NO_LIBRARY_REPLACEMENT 4 4 0 0 0 0 0 0 - -syscon misc RRQ 1 1 1 1 1 1 1 0 # unix consensus -syscon misc RTF_NOFORWARD 0x1000 0x1000 0 0 0 0 0 0 -syscon misc RTF_NOPMTUDISC 0x4000 0x4000 0 0 0 0 0 0 - -syscon misc SARMAG 8 8 8 8 8 8 8 0 # unix consensus -syscon misc SEGSIZE 0x0200 0x0200 0x0200 0x0200 0x0200 0x0200 0x0200 0 # unix consensus -syscon misc SEND_DIAGNOSTIC 29 29 0 0 0 0 0 0 -syscon misc SEND_VOLUME_TAG 182 182 0 0 0 0 0 0 - -syscon misc SET_LIMITS 51 51 0 0 0 0 0 0 -syscon misc SET_WINDOW 36 36 0 0 0 0 0 0 - -syscon misc SFD_CLOEXEC 0x080000 0x080000 0 0 0 0 0 0 -syscon misc SFD_NONBLOCK 0x0800 0x0800 0 0 0 0 0 0 - -syscon misc SUBCMDMASK 255 255 255 255 255 255 255 0 # unix consensus -syscon misc SUBCMDSHIFT 8 8 8 8 8 8 8 0 # unix consensus -syscon misc SYMTYPE 50 50 50 50 50 50 50 0 # unix consensus -syscon misc TGEXEC 8 8 8 8 8 8 8 0 # unix consensus -syscon misc TGREAD 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0 # unix consensus -syscon misc TGWRITE 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0 # unix consensus -syscon misc TMAGLEN 6 6 6 6 6 6 6 0 # unix consensus -syscon misc TOEXEC 1 1 1 1 1 1 1 0 # unix consensus -syscon misc TOREAD 4 4 4 4 4 4 4 0 # unix consensus -syscon misc TOWRITE 2 2 2 2 2 2 2 0 # unix consensus -syscon misc TRANSIENT 4 4 4 4 4 4 4 0 # unix consensus -syscon misc TSGID 0x0400 0x0400 0x0400 0x0400 0x0400 0x0400 0x0400 0 # unix consensus -syscon misc TSUID 0x0800 0x0800 0x0800 0x0800 0x0800 0x0800 0x0800 0 # unix consensus -syscon misc TSVTX 0x0200 0x0200 0x0200 0x0200 0x0200 0x0200 0x0200 0 # unix consensus -syscon misc TUEXEC 0x40 0x40 0x40 0x40 0x40 0x40 0x40 0 # unix consensus -syscon misc TUREAD 0x0100 0x0100 0x0100 0x0100 0x0100 0x0100 0x0100 0 # unix consensus -syscon misc TUWRITE 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0 # unix consensus -syscon misc TVERSLEN 2 2 2 2 2 2 2 0 # unix consensus -syscon misc WORD_BIT 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0 # unix consensus -syscon misc WRQ 2 2 2 2 2 2 2 0 # unix consensus -syscon misc SIGEV_THREAD 2 2 3 3 2 0 2 0 -syscon misc SIGEV_SIGNAL 0 0 1 1 1 0 1 0 -syscon misc SIGEV_NONE 1 1 0 0 0 0 0 0 - -syscon misc BC_BASE_MAX 99 99 99 99 99 0x7fffffff 0x7fffffff 0 -syscon misc BC_DIM_MAX 0x0800 0x0800 0x0800 0x0800 0x0800 0xffff 0xffff 0 -syscon misc BC_SCALE_MAX 99 99 99 99 99 0x7fffffff 0x7fffffff 0 -syscon misc BC_STRING_MAX 0x03e8 0x03e8 0x03e8 0x03e8 0x03e8 0x7fffffff 0x7fffffff 0 - -syscon misc ABORTED_COMMAND 11 11 0 0 0 0 0 0 -syscon misc ACORE 0 0 8 8 8 8 8 0 # bsd consensus -syscon misc AFORK 0 0 1 1 1 1 1 0 # bsd consensus -syscon misc AIO_ALLDONE 2 2 1 1 3 0 0 0 -syscon misc AIO_NOTCANCELED 1 1 4 4 2 0 0 0 -syscon misc AIO_CANCELED 0 0 2 2 1 0 0 0 # baud rates # @@ -2095,4 +1981,4 @@ syscon misc UL_SETFSIZE 2 2 2 2 2 0 0 0 syscon misc XATTR_CREATE 1 1 2 2 0 0 0 0 syscon misc XATTR_REPLACE 2 2 4 4 0 0 0 0 -# https://youtu.be/GUQUD3IMbb4?t=85 +# https://youtu.be/3SNBXoWs4GM diff --git a/libc/sysv/consts/ABORTED_COMMAND.S b/libc/sysv/consts/ABORTED_COMMAND.S deleted file mode 100644 index afb9abbae..000000000 --- a/libc/sysv/consts/ABORTED_COMMAND.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,ABORTED_COMMAND,11,11,0,0,0,0,0,0 diff --git a/libc/sysv/consts/ACORE.S b/libc/sysv/consts/ACORE.S deleted file mode 100644 index 72447040a..000000000 --- a/libc/sysv/consts/ACORE.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,ACORE,0,0,8,8,8,8,8,0 diff --git a/libc/sysv/consts/AFORK.S b/libc/sysv/consts/AFORK.S deleted file mode 100644 index 992cacc80..000000000 --- a/libc/sysv/consts/AFORK.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,AFORK,0,0,1,1,1,1,1,0 diff --git a/libc/sysv/consts/AIO_ALLDONE.S b/libc/sysv/consts/AIO_ALLDONE.S deleted file mode 100644 index 63584ff7a..000000000 --- a/libc/sysv/consts/AIO_ALLDONE.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,AIO_ALLDONE,2,2,1,1,3,0,0,0 diff --git a/libc/sysv/consts/AIO_CANCELED.S b/libc/sysv/consts/AIO_CANCELED.S deleted file mode 100644 index 6124168e4..000000000 --- a/libc/sysv/consts/AIO_CANCELED.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,AIO_CANCELED,0,0,2,2,1,0,0,0 diff --git a/libc/sysv/consts/AIO_NOTCANCELED.S b/libc/sysv/consts/AIO_NOTCANCELED.S deleted file mode 100644 index 6ef85d564..000000000 --- a/libc/sysv/consts/AIO_NOTCANCELED.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,AIO_NOTCANCELED,1,1,4,4,2,0,0,0 diff --git a/libc/sysv/consts/BC_BASE_MAX.S b/libc/sysv/consts/BC_BASE_MAX.S deleted file mode 100644 index b6bc0ec08..000000000 --- a/libc/sysv/consts/BC_BASE_MAX.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,BC_BASE_MAX,99,99,99,99,99,0x7fffffff,0x7fffffff,0 diff --git a/libc/sysv/consts/BC_DIM_MAX.S b/libc/sysv/consts/BC_DIM_MAX.S deleted file mode 100644 index 267a893bc..000000000 --- a/libc/sysv/consts/BC_DIM_MAX.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,BC_DIM_MAX,0x0800,0x0800,0x0800,0x0800,0x0800,0xffff,0xffff,0 diff --git a/libc/sysv/consts/BC_SCALE_MAX.S b/libc/sysv/consts/BC_SCALE_MAX.S deleted file mode 100644 index c7fd64227..000000000 --- a/libc/sysv/consts/BC_SCALE_MAX.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,BC_SCALE_MAX,99,99,99,99,99,0x7fffffff,0x7fffffff,0 diff --git a/libc/sysv/consts/BC_STRING_MAX.S b/libc/sysv/consts/BC_STRING_MAX.S deleted file mode 100644 index 883450d84..000000000 --- a/libc/sysv/consts/BC_STRING_MAX.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,BC_STRING_MAX,0x03e8,0x03e8,0x03e8,0x03e8,0x03e8,0x7fffffff,0x7fffffff,0 diff --git a/libc/sysv/consts/FIFOTYPE.S b/libc/sysv/consts/FIFOTYPE.S deleted file mode 100644 index 544cea69d..000000000 --- a/libc/sysv/consts/FIFOTYPE.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,FIFOTYPE,54,54,54,54,54,54,54,0 diff --git a/libc/sysv/consts/GRPQUOTA.S b/libc/sysv/consts/GRPQUOTA.S deleted file mode 100644 index 0418f4462..000000000 --- a/libc/sysv/consts/GRPQUOTA.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,GRPQUOTA,1,1,1,1,1,1,1,0 diff --git a/libc/sysv/consts/IF_NAMESIZE.S b/libc/sysv/consts/IF_NAMESIZE.S deleted file mode 100644 index 314fb2084..000000000 --- a/libc/sysv/consts/IF_NAMESIZE.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,IF_NAMESIZE,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0 diff --git a/libc/sysv/consts/INTERMEDIATE_C_GOOD.S b/libc/sysv/consts/INTERMEDIATE_C_GOOD.S deleted file mode 100644 index ced82c458..000000000 --- a/libc/sysv/consts/INTERMEDIATE_C_GOOD.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,INTERMEDIATE_C_GOOD,10,10,0,0,0,0,0,0 diff --git a/libc/sysv/consts/INTERMEDIATE_GOOD.S b/libc/sysv/consts/INTERMEDIATE_GOOD.S deleted file mode 100644 index 9a500bbf2..000000000 --- a/libc/sysv/consts/INTERMEDIATE_GOOD.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,INTERMEDIATE_GOOD,8,8,0,0,0,0,0,0 diff --git a/libc/sysv/consts/IP6F_MORE_FRAG.S b/libc/sysv/consts/IP6F_MORE_FRAG.S deleted file mode 100644 index cfa0f7681..000000000 --- a/libc/sysv/consts/IP6F_MORE_FRAG.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,IP6F_MORE_FRAG,0x0100,0x0100,0x0100,0x0100,0x0100,0x0100,0x0100,0x0100 diff --git a/libc/sysv/consts/IP6F_OFF_MASK.S b/libc/sysv/consts/IP6F_OFF_MASK.S deleted file mode 100644 index 1c927ebfe..000000000 --- a/libc/sysv/consts/IP6F_OFF_MASK.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,IP6F_OFF_MASK,0xf8ff,0xf8ff,0xf8ff,0xf8ff,0xf8ff,0xf8ff,0xf8ff,0xf8ff diff --git a/libc/sysv/consts/IP6F_RESERVED_MASK.S b/libc/sysv/consts/IP6F_RESERVED_MASK.S deleted file mode 100644 index 19997c100..000000000 --- a/libc/sysv/consts/IP6F_RESERVED_MASK.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,IP6F_RESERVED_MASK,0x0600,0x0600,0x0600,0x0600,0x0600,0x0600,0x0600,0x0600 diff --git a/libc/sysv/consts/LINE_MAX.S b/libc/sysv/consts/LINE_MAX.S deleted file mode 100644 index 38afae7a3..000000000 --- a/libc/sysv/consts/LINE_MAX.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,LINE_MAX,0x0800,0x0800,0x0800,0x0800,0x0800,0x0800,0x0800,0 diff --git a/libc/sysv/consts/LINKED_CMD_COMPLETE.S b/libc/sysv/consts/LINKED_CMD_COMPLETE.S deleted file mode 100644 index cbfd020ff..000000000 --- a/libc/sysv/consts/LINKED_CMD_COMPLETE.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,LINKED_CMD_COMPLETE,10,10,0,0,0,0,0,0 diff --git a/libc/sysv/consts/LINKED_FLG_CMD_COMPLETE.S b/libc/sysv/consts/LINKED_FLG_CMD_COMPLETE.S deleted file mode 100644 index e1b654e9c..000000000 --- a/libc/sysv/consts/LINKED_FLG_CMD_COMPLETE.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,LINKED_FLG_CMD_COMPLETE,11,11,0,0,0,0,0,0 diff --git a/libc/sysv/consts/LNKTYPE.S b/libc/sysv/consts/LNKTYPE.S deleted file mode 100644 index 5c92f4df1..000000000 --- a/libc/sysv/consts/LNKTYPE.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,LNKTYPE,49,49,49,49,49,49,49,0 diff --git a/libc/sysv/consts/LOCK_UNLOCK_CACHE.S b/libc/sysv/consts/LOCK_UNLOCK_CACHE.S deleted file mode 100644 index 79f898e0f..000000000 --- a/libc/sysv/consts/LOCK_UNLOCK_CACHE.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon lock,LOCK_UNLOCK_CACHE,54,54,0,0,0,0,0,0 diff --git a/libc/sysv/consts/L_INCR.S b/libc/sysv/consts/L_INCR.S deleted file mode 100644 index 7fa6739c4..000000000 --- a/libc/sysv/consts/L_INCR.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,L_INCR,1,1,1,1,1,1,1,0 diff --git a/libc/sysv/consts/L_SET.S b/libc/sysv/consts/L_SET.S deleted file mode 100644 index 17fa679f8..000000000 --- a/libc/sysv/consts/L_SET.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,L_SET,0,0,0,0,0,0,0,0 diff --git a/libc/sysv/consts/L_XTND.S b/libc/sysv/consts/L_XTND.S deleted file mode 100644 index d21b49d21..000000000 --- a/libc/sysv/consts/L_XTND.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,L_XTND,2,2,2,2,2,2,2,0 diff --git a/libc/sysv/consts/MAP_NOFORK.S b/libc/sysv/consts/MAP_NOFORK.S deleted file mode 100644 index 04b0363b6..000000000 --- a/libc/sysv/consts/MAP_NOFORK.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon mmap,MAP_NOFORK,0,0,0,0,0,0,0,0x10000000 diff --git a/libc/sysv/consts/MATH_ERREXCEPT.S b/libc/sysv/consts/MATH_ERREXCEPT.S deleted file mode 100644 index b4b21e1d4..000000000 --- a/libc/sysv/consts/MATH_ERREXCEPT.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,MATH_ERREXCEPT,2,2,2,2,2,2,2,0 diff --git a/libc/sysv/consts/MATH_ERRNO.S b/libc/sysv/consts/MATH_ERRNO.S deleted file mode 100644 index 1feafb8f4..000000000 --- a/libc/sysv/consts/MATH_ERRNO.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,MATH_ERRNO,1,1,1,1,1,1,1,0 diff --git a/libc/sysv/consts/MAXNAMLEN.S b/libc/sysv/consts/MAXNAMLEN.S deleted file mode 100644 index 39e41287f..000000000 --- a/libc/sysv/consts/MAXNAMLEN.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,MAXNAMLEN,255,255,255,255,255,255,255,0 diff --git a/libc/sysv/consts/MAXQUOTAS.S b/libc/sysv/consts/MAXQUOTAS.S deleted file mode 100644 index 6f75185e0..000000000 --- a/libc/sysv/consts/MAXQUOTAS.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,MAXQUOTAS,2,2,2,2,2,2,2,0 diff --git a/libc/sysv/consts/MEDIUM_ERROR.S b/libc/sysv/consts/MEDIUM_ERROR.S deleted file mode 100644 index 5197ba6e3..000000000 --- a/libc/sysv/consts/MEDIUM_ERROR.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,MEDIUM_ERROR,3,3,0,0,0,0,0,0 diff --git a/libc/sysv/consts/MEDIUM_SCAN.S b/libc/sysv/consts/MEDIUM_SCAN.S deleted file mode 100644 index abe6fdede..000000000 --- a/libc/sysv/consts/MEDIUM_SCAN.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,MEDIUM_SCAN,56,56,0,0,0,0,0,0 diff --git a/libc/sysv/consts/MSG_BATCH.S b/libc/sysv/consts/MSG_BATCH.S deleted file mode 100644 index e171f4f2f..000000000 --- a/libc/sysv/consts/MSG_BATCH.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_BATCH,0x040000,0x040000,0,0,0,0,0,0 diff --git a/libc/sysv/consts/MSG_BCAST.S b/libc/sysv/consts/MSG_BCAST.S deleted file mode 100644 index 082634257..000000000 --- a/libc/sysv/consts/MSG_BCAST.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_BCAST,0,0,0,0,0,0x100,0x100,0 diff --git a/libc/sysv/consts/MSG_CMSG_CLOEXEC.S b/libc/sysv/consts/MSG_CMSG_CLOEXEC.S deleted file mode 100644 index ea66034e3..000000000 --- a/libc/sysv/consts/MSG_CMSG_CLOEXEC.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_CMSG_CLOEXEC,0x40000000,0x40000000,0,0,0x040000,0x0800,0x0800,0 diff --git a/libc/sysv/consts/MSG_CONFIRM.S b/libc/sysv/consts/MSG_CONFIRM.S deleted file mode 100644 index 1948be63a..000000000 --- a/libc/sysv/consts/MSG_CONFIRM.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_CONFIRM,0x0800,0x0800,0,0,0,0,0,0 diff --git a/libc/sysv/consts/MSG_EOF.S b/libc/sysv/consts/MSG_EOF.S deleted file mode 100644 index 3e8168a2a..000000000 --- a/libc/sysv/consts/MSG_EOF.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_EOF,0x0200,0x0200,0x0100,0x0100,0x0100,0,0,0 diff --git a/libc/sysv/consts/MSG_EOR.S b/libc/sysv/consts/MSG_EOR.S deleted file mode 100644 index fe773b16a..000000000 --- a/libc/sysv/consts/MSG_EOR.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_EOR,0x80,0x80,8,8,8,8,8,0 diff --git a/libc/sysv/consts/MSG_ERRQUEUE.S b/libc/sysv/consts/MSG_ERRQUEUE.S deleted file mode 100644 index 02f1934ff..000000000 --- a/libc/sysv/consts/MSG_ERRQUEUE.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_ERRQUEUE,0x2000,0x2000,0,0,0,0,0,0x1000 diff --git a/libc/sysv/consts/MSG_EXCEPT.S b/libc/sysv/consts/MSG_EXCEPT.S deleted file mode 100644 index da072f0a9..000000000 --- a/libc/sysv/consts/MSG_EXCEPT.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_EXCEPT,0x2000,0x2000,0,0,0,0,0,0 diff --git a/libc/sysv/consts/MSG_FASTOPEN.S b/libc/sysv/consts/MSG_FASTOPEN.S index ff034a75b..17aa7bab6 100644 --- a/libc/sysv/consts/MSG_FASTOPEN.S +++ b/libc/sysv/consts/MSG_FASTOPEN.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_FASTOPEN,0x20000000,0x20000000,0,0,0,0,0,0 +.syscon msg,MSG_FASTOPEN,0x20000000,0x20000000,-1,-1,-1,-1,-1,-1 diff --git a/libc/sysv/consts/MSG_FIN.S b/libc/sysv/consts/MSG_FIN.S deleted file mode 100644 index f0631e333..000000000 --- a/libc/sysv/consts/MSG_FIN.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_FIN,0x0200,0x0200,0x0100,0x0100,0x0100,0,0,0 diff --git a/libc/sysv/consts/MSG_INFO.S b/libc/sysv/consts/MSG_INFO.S deleted file mode 100644 index d882e3a75..000000000 --- a/libc/sysv/consts/MSG_INFO.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_INFO,12,12,0,0,0,0,0,0 diff --git a/libc/sysv/consts/MSG_MCAST.S b/libc/sysv/consts/MSG_MCAST.S deleted file mode 100644 index 0f68f5321..000000000 --- a/libc/sysv/consts/MSG_MCAST.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_MCAST,0,0,0,0,0,0x200,0x200,0 diff --git a/libc/sysv/consts/MSG_MORE.S b/libc/sysv/consts/MSG_MORE.S deleted file mode 100644 index fa8717d4f..000000000 --- a/libc/sysv/consts/MSG_MORE.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_MORE,0x8000,0x8000,0,0,0,0,0,0 diff --git a/libc/sysv/consts/MSG_NOERROR.S b/libc/sysv/consts/MSG_NOERROR.S deleted file mode 100644 index 22764bb8d..000000000 --- a/libc/sysv/consts/MSG_NOERROR.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_NOERROR,0x1000,0x1000,0x1000,0x1000,0x1000,0x1000,0x1000,0 diff --git a/libc/sysv/consts/MSG_NOSIGNAL.S b/libc/sysv/consts/MSG_NOSIGNAL.S index 48be7e922..1a0539eef 100644 --- a/libc/sysv/consts/MSG_NOSIGNAL.S +++ b/libc/sysv/consts/MSG_NOSIGNAL.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_NOSIGNAL,0x4000,0x4000,0x80000,0x80000,0x020000,0x0400,0x0400,0 +.syscon msg,MSG_NOSIGNAL,0x4000,0x4000,0x80000,0x80000,0x020000,0x0400,0x0400,0x10000000 diff --git a/libc/sysv/consts/MSG_NOTIFICATION.S b/libc/sysv/consts/MSG_NOTIFICATION.S deleted file mode 100644 index 7503b5a50..000000000 --- a/libc/sysv/consts/MSG_NOTIFICATION.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_NOTIFICATION,0x8000,0x8000,0,0,0x2000,0,0x4000,0 diff --git a/libc/sysv/consts/MSG_PARITY_ERROR.S b/libc/sysv/consts/MSG_PARITY_ERROR.S deleted file mode 100644 index 7abd7fafd..000000000 --- a/libc/sysv/consts/MSG_PARITY_ERROR.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_PARITY_ERROR,9,9,0,0,0,0,0,0 diff --git a/libc/sysv/consts/MSG_PROXY.S b/libc/sysv/consts/MSG_PROXY.S deleted file mode 100644 index e6216ce64..000000000 --- a/libc/sysv/consts/MSG_PROXY.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_PROXY,0x10,0x10,0,0,0,0,0,0 diff --git a/libc/sysv/consts/MSG_RST.S b/libc/sysv/consts/MSG_RST.S deleted file mode 100644 index 3bed0246b..000000000 --- a/libc/sysv/consts/MSG_RST.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_RST,0x1000,0x1000,0,0,0,0,0,0 diff --git a/libc/sysv/consts/MSG_STAT.S b/libc/sysv/consts/MSG_STAT.S deleted file mode 100644 index cdee6a110..000000000 --- a/libc/sysv/consts/MSG_STAT.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_STAT,11,11,0,0,0,0,0,0 diff --git a/libc/sysv/consts/MSG_SYN.S b/libc/sysv/consts/MSG_SYN.S deleted file mode 100644 index b57fbeb86..000000000 --- a/libc/sysv/consts/MSG_SYN.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_SYN,0x0400,0x0400,0,0,0,0,0,0 diff --git a/libc/sysv/consts/MSG_WAITFORONE.S b/libc/sysv/consts/MSG_WAITFORONE.S deleted file mode 100644 index e89b311e2..000000000 --- a/libc/sysv/consts/MSG_WAITFORONE.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon msg,MSG_WAITFORONE,0x010000,0x010000,0,0,0x080000,0,0x2000,0 diff --git a/libc/sysv/consts/NBBY.S b/libc/sysv/consts/NBBY.S deleted file mode 100644 index 72b368fb5..000000000 --- a/libc/sysv/consts/NBBY.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,NBBY,8,8,8,8,8,8,8,0 diff --git a/libc/sysv/consts/NR_DQHASH.S b/libc/sysv/consts/NR_DQHASH.S deleted file mode 100644 index c485c1cd4..000000000 --- a/libc/sysv/consts/NR_DQHASH.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,NR_DQHASH,43,43,0,0,0,0,0,0 diff --git a/libc/sysv/consts/NR_DQUOTS.S b/libc/sysv/consts/NR_DQUOTS.S deleted file mode 100644 index 43ac329ef..000000000 --- a/libc/sysv/consts/NR_DQUOTS.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,NR_DQUOTS,0x0100,0x0100,0,0,0,0,0,0 diff --git a/libc/sysv/consts/PERSISTENT_RESERVE_IN.S b/libc/sysv/consts/PERSISTENT_RESERVE_IN.S deleted file mode 100644 index de777b3c6..000000000 --- a/libc/sysv/consts/PERSISTENT_RESERVE_IN.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,PERSISTENT_RESERVE_IN,94,94,0,0,0,0,0,0 diff --git a/libc/sysv/consts/PERSISTENT_RESERVE_OUT.S b/libc/sysv/consts/PERSISTENT_RESERVE_OUT.S deleted file mode 100644 index 791a63475..000000000 --- a/libc/sysv/consts/PERSISTENT_RESERVE_OUT.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,PERSISTENT_RESERVE_OUT,95,95,0,0,0,0,0,0 diff --git a/libc/sysv/consts/PRELIM.S b/libc/sysv/consts/PRELIM.S deleted file mode 100644 index 0d97154e6..000000000 --- a/libc/sysv/consts/PRELIM.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,PRELIM,1,1,1,1,1,1,1,0 diff --git a/libc/sysv/consts/REGTYPE.S b/libc/sysv/consts/REGTYPE.S deleted file mode 100644 index 23ea4bcc2..000000000 --- a/libc/sysv/consts/REGTYPE.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,REGTYPE,48,48,48,48,48,48,48,0 diff --git a/libc/sysv/consts/RES_PRF_CLASS.S b/libc/sysv/consts/RES_PRF_CLASS.S deleted file mode 100644 index ec3d33e78..000000000 --- a/libc/sysv/consts/RES_PRF_CLASS.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,RES_PRF_CLASS,4,4,4,4,4,4,4,0 diff --git a/libc/sysv/consts/RHF_GUARANTEE_START_INIT.S b/libc/sysv/consts/RHF_GUARANTEE_START_INIT.S deleted file mode 100644 index 6cfba6bb4..000000000 --- a/libc/sysv/consts/RHF_GUARANTEE_START_INIT.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,RHF_GUARANTEE_START_INIT,0x80,0x80,0,0,0,0,0,0 diff --git a/libc/sysv/consts/RHF_NO_LIBRARY_REPLACEMENT.S b/libc/sysv/consts/RHF_NO_LIBRARY_REPLACEMENT.S deleted file mode 100644 index 7129c23c6..000000000 --- a/libc/sysv/consts/RHF_NO_LIBRARY_REPLACEMENT.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,RHF_NO_LIBRARY_REPLACEMENT,4,4,0,0,0,0,0,0 diff --git a/libc/sysv/consts/RRQ.S b/libc/sysv/consts/RRQ.S deleted file mode 100644 index 2d714fad7..000000000 --- a/libc/sysv/consts/RRQ.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,RRQ,1,1,1,1,1,1,1,0 diff --git a/libc/sysv/consts/RTF_NOFORWARD.S b/libc/sysv/consts/RTF_NOFORWARD.S deleted file mode 100644 index bd1a57d66..000000000 --- a/libc/sysv/consts/RTF_NOFORWARD.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,RTF_NOFORWARD,0x1000,0x1000,0,0,0,0,0,0 diff --git a/libc/sysv/consts/RTF_NOPMTUDISC.S b/libc/sysv/consts/RTF_NOPMTUDISC.S deleted file mode 100644 index b31c84352..000000000 --- a/libc/sysv/consts/RTF_NOPMTUDISC.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,RTF_NOPMTUDISC,0x4000,0x4000,0,0,0,0,0,0 diff --git a/libc/sysv/consts/SARMAG.S b/libc/sysv/consts/SARMAG.S deleted file mode 100644 index cae2eee96..000000000 --- a/libc/sysv/consts/SARMAG.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,SARMAG,8,8,8,8,8,8,8,0 diff --git a/libc/sysv/consts/SEGSIZE.S b/libc/sysv/consts/SEGSIZE.S deleted file mode 100644 index ae243d93f..000000000 --- a/libc/sysv/consts/SEGSIZE.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,SEGSIZE,0x0200,0x0200,0x0200,0x0200,0x0200,0x0200,0x0200,0 diff --git a/libc/sysv/consts/SEND_DIAGNOSTIC.S b/libc/sysv/consts/SEND_DIAGNOSTIC.S deleted file mode 100644 index 83da87ff2..000000000 --- a/libc/sysv/consts/SEND_DIAGNOSTIC.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,SEND_DIAGNOSTIC,29,29,0,0,0,0,0,0 diff --git a/libc/sysv/consts/SEND_VOLUME_TAG.S b/libc/sysv/consts/SEND_VOLUME_TAG.S deleted file mode 100644 index 4303e011d..000000000 --- a/libc/sysv/consts/SEND_VOLUME_TAG.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,SEND_VOLUME_TAG,182,182,0,0,0,0,0,0 diff --git a/libc/sysv/consts/SET_LIMITS.S b/libc/sysv/consts/SET_LIMITS.S deleted file mode 100644 index de5e6f1b8..000000000 --- a/libc/sysv/consts/SET_LIMITS.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,SET_LIMITS,51,51,0,0,0,0,0,0 diff --git a/libc/sysv/consts/SET_WINDOW.S b/libc/sysv/consts/SET_WINDOW.S deleted file mode 100644 index 5c39025c8..000000000 --- a/libc/sysv/consts/SET_WINDOW.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,SET_WINDOW,36,36,0,0,0,0,0,0 diff --git a/libc/sysv/consts/SFD_CLOEXEC.S b/libc/sysv/consts/SFD_CLOEXEC.S deleted file mode 100644 index 8cdb0e1ce..000000000 --- a/libc/sysv/consts/SFD_CLOEXEC.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,SFD_CLOEXEC,0x080000,0x080000,0,0,0,0,0,0 diff --git a/libc/sysv/consts/SFD_NONBLOCK.S b/libc/sysv/consts/SFD_NONBLOCK.S deleted file mode 100644 index 7c81b0a84..000000000 --- a/libc/sysv/consts/SFD_NONBLOCK.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,SFD_NONBLOCK,0x0800,0x0800,0,0,0,0,0,0 diff --git a/libc/sysv/consts/SIGEV_NONE.S b/libc/sysv/consts/SIGEV_NONE.S deleted file mode 100644 index f36a315c9..000000000 --- a/libc/sysv/consts/SIGEV_NONE.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,SIGEV_NONE,1,1,0,0,0,0,0,0 diff --git a/libc/sysv/consts/SIGEV_SIGNAL.S b/libc/sysv/consts/SIGEV_SIGNAL.S deleted file mode 100644 index 083f4d54c..000000000 --- a/libc/sysv/consts/SIGEV_SIGNAL.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,SIGEV_SIGNAL,0,0,1,1,1,0,1,0 diff --git a/libc/sysv/consts/SIGEV_THREAD.S b/libc/sysv/consts/SIGEV_THREAD.S deleted file mode 100644 index 0b4173693..000000000 --- a/libc/sysv/consts/SIGEV_THREAD.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,SIGEV_THREAD,2,2,3,3,2,0,2,0 diff --git a/libc/sysv/consts/SUBCMDMASK.S b/libc/sysv/consts/SUBCMDMASK.S deleted file mode 100644 index 84d9e6756..000000000 --- a/libc/sysv/consts/SUBCMDMASK.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,SUBCMDMASK,255,255,255,255,255,255,255,0 diff --git a/libc/sysv/consts/SUBCMDSHIFT.S b/libc/sysv/consts/SUBCMDSHIFT.S deleted file mode 100644 index bb41322bc..000000000 --- a/libc/sysv/consts/SUBCMDSHIFT.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,SUBCMDSHIFT,8,8,8,8,8,8,8,0 diff --git a/libc/sysv/consts/SYMTYPE.S b/libc/sysv/consts/SYMTYPE.S deleted file mode 100644 index a285acd5e..000000000 --- a/libc/sysv/consts/SYMTYPE.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,SYMTYPE,50,50,50,50,50,50,50,0 diff --git a/libc/sysv/consts/TGEXEC.S b/libc/sysv/consts/TGEXEC.S deleted file mode 100644 index 67216cff5..000000000 --- a/libc/sysv/consts/TGEXEC.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,TGEXEC,8,8,8,8,8,8,8,0 diff --git a/libc/sysv/consts/TGREAD.S b/libc/sysv/consts/TGREAD.S deleted file mode 100644 index 0d8ac6ba5..000000000 --- a/libc/sysv/consts/TGREAD.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,TGREAD,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0 diff --git a/libc/sysv/consts/TGWRITE.S b/libc/sysv/consts/TGWRITE.S deleted file mode 100644 index f4b54f296..000000000 --- a/libc/sysv/consts/TGWRITE.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,TGWRITE,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0 diff --git a/libc/sysv/consts/TMAGLEN.S b/libc/sysv/consts/TMAGLEN.S deleted file mode 100644 index 132be740f..000000000 --- a/libc/sysv/consts/TMAGLEN.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,TMAGLEN,6,6,6,6,6,6,6,0 diff --git a/libc/sysv/consts/TOEXEC.S b/libc/sysv/consts/TOEXEC.S deleted file mode 100644 index 55f00072e..000000000 --- a/libc/sysv/consts/TOEXEC.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,TOEXEC,1,1,1,1,1,1,1,0 diff --git a/libc/sysv/consts/TOREAD.S b/libc/sysv/consts/TOREAD.S deleted file mode 100644 index 802694d71..000000000 --- a/libc/sysv/consts/TOREAD.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,TOREAD,4,4,4,4,4,4,4,0 diff --git a/libc/sysv/consts/TOWRITE.S b/libc/sysv/consts/TOWRITE.S deleted file mode 100644 index 14bb0c7ca..000000000 --- a/libc/sysv/consts/TOWRITE.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,TOWRITE,2,2,2,2,2,2,2,0 diff --git a/libc/sysv/consts/TRANSIENT.S b/libc/sysv/consts/TRANSIENT.S deleted file mode 100644 index c7f7807e2..000000000 --- a/libc/sysv/consts/TRANSIENT.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,TRANSIENT,4,4,4,4,4,4,4,0 diff --git a/libc/sysv/consts/TSGID.S b/libc/sysv/consts/TSGID.S deleted file mode 100644 index 253a91ac7..000000000 --- a/libc/sysv/consts/TSGID.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,TSGID,0x0400,0x0400,0x0400,0x0400,0x0400,0x0400,0x0400,0 diff --git a/libc/sysv/consts/TSUID.S b/libc/sysv/consts/TSUID.S deleted file mode 100644 index c369b647b..000000000 --- a/libc/sysv/consts/TSUID.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,TSUID,0x0800,0x0800,0x0800,0x0800,0x0800,0x0800,0x0800,0 diff --git a/libc/sysv/consts/TSVTX.S b/libc/sysv/consts/TSVTX.S deleted file mode 100644 index 8269bb0ff..000000000 --- a/libc/sysv/consts/TSVTX.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,TSVTX,0x0200,0x0200,0x0200,0x0200,0x0200,0x0200,0x0200,0 diff --git a/libc/sysv/consts/TUEXEC.S b/libc/sysv/consts/TUEXEC.S deleted file mode 100644 index 0a810cd15..000000000 --- a/libc/sysv/consts/TUEXEC.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,TUEXEC,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0 diff --git a/libc/sysv/consts/TUREAD.S b/libc/sysv/consts/TUREAD.S deleted file mode 100644 index aa151e389..000000000 --- a/libc/sysv/consts/TUREAD.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,TUREAD,0x0100,0x0100,0x0100,0x0100,0x0100,0x0100,0x0100,0 diff --git a/libc/sysv/consts/TUWRITE.S b/libc/sysv/consts/TUWRITE.S deleted file mode 100644 index 46e307643..000000000 --- a/libc/sysv/consts/TUWRITE.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,TUWRITE,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0 diff --git a/libc/sysv/consts/TVERSLEN.S b/libc/sysv/consts/TVERSLEN.S deleted file mode 100644 index d09006b08..000000000 --- a/libc/sysv/consts/TVERSLEN.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,TVERSLEN,2,2,2,2,2,2,2,0 diff --git a/libc/sysv/consts/WORD_BIT.S b/libc/sysv/consts/WORD_BIT.S deleted file mode 100644 index 1dc138b99..000000000 --- a/libc/sysv/consts/WORD_BIT.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,WORD_BIT,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0 diff --git a/libc/sysv/consts/WRQ.S b/libc/sysv/consts/WRQ.S deleted file mode 100644 index c101d655b..000000000 --- a/libc/sysv/consts/WRQ.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon misc,WRQ,2,2,2,2,2,2,2,0 diff --git a/libc/sysv/consts/_MINSIGSTKSZ.S b/libc/sysv/consts/_MINSIGSTKSZ.S index b55cbcca9..94d7efe53 100644 --- a/libc/sysv/consts/_MINSIGSTKSZ.S +++ b/libc/sysv/consts/_MINSIGSTKSZ.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon limits,_MINSIGSTKSZ,2048,2048,32768,32768,4096,12288,8192,2048 +.syscon limits,_MINSIGSTKSZ,2048,6144,8192,32768,6656,14336,8192,2048 diff --git a/libc/sysv/consts/_SIGSTKSZ.S b/libc/sysv/consts/_SIGSTKSZ.S index 6347f2877..8484e6596 100644 --- a/libc/sysv/consts/_SIGSTKSZ.S +++ b/libc/sysv/consts/_SIGSTKSZ.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon limits,_SIGSTKSZ,8192,2048,131072,131072,36864,28672,40960,8192 +.syscon limits,_SIGSTKSZ,10240,10240,131072,131072,36864,28672,40960,10240 diff --git a/libc/sysv/consts/map.h b/libc/sysv/consts/map.h index ae719ea0b..20ed8bf51 100644 --- a/libc/sysv/consts/map.h +++ b/libc/sysv/consts/map.h @@ -19,7 +19,6 @@ extern const int MAP_JIT; extern const int MAP_LOCKED; extern const int MAP_NOCACHE; extern const int MAP_NOEXTEND; -extern const int MAP_NOFORK; extern const int MAP_NONBLOCK; extern const int MAP_NORESERVE; extern const int MAP_NOSYNC; diff --git a/libc/sysv/consts/msg.h b/libc/sysv/consts/msg.h index 74deaca26..3694fc6f6 100644 --- a/libc/sysv/consts/msg.h +++ b/libc/sysv/consts/msg.h @@ -2,41 +2,18 @@ #define COSMOPOLITAN_LIBC_SYSV_CONSTS_MSG_H_ COSMOPOLITAN_C_START_ -extern const int MSG_BATCH; -extern const int MSG_BCAST; -extern const int MSG_CMSG_CLOEXEC; -extern const int MSG_CONFIRM; -extern const int MSG_CTRUNC; -extern const int MSG_DONTROUTE; extern const int MSG_DONTWAIT; -extern const int MSG_EOF; -extern const int MSG_EOR; -extern const int MSG_ERRQUEUE; -extern const int MSG_EXCEPT; -extern const int MSG_FASTOPEN; -extern const int MSG_FIN; -extern const int MSG_INFO; -extern const int MSG_MCAST; -extern const int MSG_MORE; -extern const int MSG_NOERROR; -extern const int MSG_NOSIGNAL; -extern const int MSG_NOTIFICATION; -extern const int MSG_OOB; -extern const int MSG_PARITY_ERROR; -extern const int MSG_PEEK; -extern const int MSG_PROXY; -extern const int MSG_RST; -extern const int MSG_STAT; -extern const int MSG_SYN; -extern const int MSG_TRUNC; extern const int MSG_WAITALL; -extern const int MSG_WAITFORONE; +extern const int MSG_NOSIGNAL; +extern const int MSG_TRUNC; +extern const int MSG_CTRUNC; +extern const int MSG_FASTOPEN; /* linux only */ #define MSG_OOB 1 #define MSG_PEEK 2 #define MSG_DONTROUTE 4 #define MSG_DONTWAIT MSG_DONTWAIT -#define MSG_FASTOPEN MSG_FASTOPEN +#define MSG_NOSIGNAL MSG_NOSIGNAL #define MSG_WAITALL MSG_WAITALL #define MSG_TRUNC MSG_TRUNC #define MSG_CTRUNC MSG_CTRUNC diff --git a/libc/sysv/consts/sig.h b/libc/sysv/consts/sig.h index 3a66c711b..d0c6808d2 100644 --- a/libc/sysv/consts/sig.h +++ b/libc/sysv/consts/sig.h @@ -2,45 +2,25 @@ #define COSMOPOLITAN_LIBC_SYSV_CONSTS_SIG_H_ COSMOPOLITAN_C_START_ -extern const int SIGABRT; -extern const int SIGALRM; extern const int SIGBUS; extern const int SIGTHR; extern const int SIGCHLD; extern const int SIGCONT; extern const int SIGEMT; -extern const int SIGFPE; -extern const int SIGHUP; -extern const int SIGILL; extern const int SIGINFO; -extern const int SIGINT; extern const int SIGIO; -extern const int SIGIOT; -extern const int SIGKILL; -extern const int SIGPIPE; extern const int SIGPOLL; -extern const int SIGPROF; extern const int SIGPWR; -extern const int SIGQUIT; extern const int SIGRTMAX; extern const int SIGRTMIN; -extern const int SIGSEGV; extern const int SIGSTKFLT; extern const int SIGSTOP; extern const int SIGSYS; -extern const int SIGTERM; -extern const int SIGTRAP; extern const int SIGTSTP; -extern const int SIGTTIN; -extern const int SIGTTOU; extern const int SIGUNUSED; extern const int SIGURG; extern const int SIGUSR1; extern const int SIGUSR2; -extern const int SIGVTALRM; -extern const int SIGWINCH; -extern const int SIGXCPU; -extern const int SIGXFSZ; extern const int SIG_BLOCK; extern const int SIG_SETMASK; diff --git a/libc/sysv/consts/ss.h b/libc/sysv/consts/ss.h index ef83ec6ec..626a696d6 100644 --- a/libc/sysv/consts/ss.h +++ b/libc/sysv/consts/ss.h @@ -8,7 +8,7 @@ extern const int _MINSIGSTKSZ; COSMOPOLITAN_C_END_ -#define SIGSTKSZ 32768 +#define SIGSTKSZ 32768 /* just itself believed to be safe */ #define MINSIGSTKSZ 32768 /* xnu defines the highest minimum */ #define SS_ONSTACK 1 #define SS_DISABLE SS_DISABLE diff --git a/libc/sysv/errno.c b/libc/sysv/errno.c index 438ee9508..038eca137 100644 --- a/libc/sysv/errno.c +++ b/libc/sysv/errno.c @@ -17,7 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/errno.h" -#include "libc/thread/tls2.internal.h" +#include "libc/thread/tls.h" /** * Global variable for last error. @@ -35,8 +35,10 @@ errno_t __errno; /** * Returns address of `errno` variable. + * + * This function promises to not clobber argument registers. */ -errno_t *__errno_location(void) { +nocallersavedregisters errno_t *__errno_location(void) { if (__tls_enabled) { return &__get_tls()->tib_errno; } else { diff --git a/libc/sysv/hostos.S b/libc/sysv/hostos.S index e4550d488..5adcfc603 100644 --- a/libc/sysv/hostos.S +++ b/libc/sysv/hostos.S @@ -22,4 +22,10 @@ .balign 8 __hostos: .quad 0 - .endfn __hostos,globl + .endobj __hostos,globl +__tls_index: + .long 0 + .endobj __tls_index,globl +__tls_enabled: + .long 0 + .endobj __tls_enabled,globl diff --git a/libc/sysv/systemfive.S b/libc/sysv/systemfive.S index 5fd782d62..178892482 100644 --- a/libc/sysv/systemfive.S +++ b/libc/sysv/systemfive.S @@ -102,8 +102,8 @@ __pid: .quad 0 .previous systemfive_cp: - push %rbp - mov %rsp,%rbp // so backtraces work + beg + pro systemfive_cancellable: // our pthread_cancel() miracle code cmpb $0,__tls_enabled(%rip) // inspired by the musl libc design! je 1f // we handle linux and bsd together! @@ -123,7 +123,7 @@ systemfive_cancellable: // our pthread_cancel() miracle code clc // no cancellable system calls exist syscall // that have 7+ args on the bsd OSes systemfive_cancellable_end: // i/o calls park here for long time - pop %rbp + epi jnc 2f neg %rax // turns bsd errno to system v errno 2: cmp $-4095,%rax // but we still check again on eintr @@ -144,11 +144,13 @@ systemfive_cancellable_end: // i/o calls park here for long time je systemfive_errno // we aren't actually cancelled jmp 4f // now we are in fact cancelled systemfive_cancel: // SIGTHR will jump here too - pop %rbp + epi 4: jmp _pthread_cancel_ack // tail call .weak _pthread_cancel_ack // must be linked if we're cancelled + end #if IsModeDbg() not_a_cancellation_point: // need BEGIN/END_CANCELLATION_POINT + beg nop .weak report_cancellation_point 5: ezlea report_cancellation_point,cx @@ -157,6 +159,7 @@ not_a_cancellation_point: // need BEGIN/END_CANCELLATION_POINT call *%rcx 6: ud2 nop + end #endif .globl systemfive_cancellable_end .globl systemfive_cancellable @@ -166,19 +169,20 @@ not_a_cancellation_point: // need BEGIN/END_CANCELLATION_POINT .Lanchorpoint: #if SupportsLinux() || SupportsMetal() systemfive_linux: + beg and $0xfff,%eax // remove nonlinux bits from ordinal cmp $0xfff,%eax // checks if unsupported by platform je systemfive_enosys // never taken branches cost nothing btr $11,%eax // 0x800 means a call is cancellable jc systemfive_cp // it is handled by the holiest code mov %rcx,%r10 // syscall instruction clobbers %rcx - push %rbp // linux never reads args from stack - mov %rsp,%rbp // having frame will help backtraces + pro // linux never reads args from stack syscall // this is known as a context switch - pop %rbp // next we check to see if it failed + epi // next we check to see if it failed cmp $-4095,%rax // system five nexgen32e abi § a.2.1 jae systemfive_error // encodes errno as neg return value ret + end .endfn systemfive_linux,globl,hidden systemfive_error: neg %eax @@ -186,27 +190,35 @@ systemfive_error: .endfn systemfive_error,globl,hidden #endif systemfive_errno: + beg xchg %eax,%ecx - .errno + call __errno_location mov %ecx,(%rax) // normalize to c library convention push $-1 // negative one is only error result pop %rax // the push pop is to save code size ret + end .endfn systemfive_errno,globl,hidden systemfive_enosys: + beg mov ENOSYS(%rip),%eax jmp systemfive_errno + end .endfn systemfive_enosys,globl,hidden #if SupportsNetbsd() systemfive_netbsd: + beg shr $4*13,%rax jmp systemfive_bsdscrub + end .endfn systemfive_netbsd,globl,hidden #endif #if SupportsOpenbsd() systemfive_openbsd: + beg shr $4*10,%rax jmp systemfive_bsdscrub + end .endfn systemfive_openbsd,globl,hidden #endif #if SupportsFreebsd() @@ -222,6 +234,7 @@ systemfive_bsdscrub: // 𝑠𝑙𝑖𝑑𝑒 .endfn systemfive_bsdscrub,globl,hidden systemfive_bsd: + beg cmp $0xfff,%ax je systemfive_enosys btr $11,%eax // checks/reset the 800 cancellable bit @@ -230,6 +243,7 @@ systemfive_bsd: syscall // bsd will need arg on stack sometimes jc systemfive_errno // bsd sets carry flag if %rax is errno ret + end .endfn systemfive_bsd #endif #if SupportsXnu() diff --git a/libc/testlib/BUILD.mk b/libc/testlib/BUILD.mk index 83fb3dd40..a81790c86 100644 --- a/libc/testlib/BUILD.mk +++ b/libc/testlib/BUILD.mk @@ -27,10 +27,12 @@ LIBC_TESTLIB_A_HDRS = \ libc/testlib/ezbench.h \ libc/testlib/fastrandomstring.h \ libc/testlib/hyperion.h \ + libc/testlib/manystack.h \ libc/testlib/moby.h \ libc/testlib/subprocess.h \ libc/testlib/testlib.h \ - libc/testlib/viewables.h + libc/testlib/trace.h \ + libc/testlib/viewables.h \ LIBC_TESTLIB_A_SRCS_S = \ libc/testlib/bench.S \ @@ -69,6 +71,7 @@ LIBC_TESTLIB_A_SRCS_C = \ libc/testlib/globals.c \ libc/testlib/hexequals.c \ libc/testlib/incrementfailed.c \ + libc/testlib/manystack.c \ libc/testlib/memoryexists.c \ libc/testlib/seterrno.c \ libc/testlib/shoulddebugbreak.c \ @@ -80,9 +83,10 @@ LIBC_TESTLIB_A_SRCS_C = \ libc/testlib/testrunner.c \ libc/testlib/thunks.c \ libc/testlib/tmptest.c \ + libc/testlib/trace.c \ libc/testlib/waitforexit.c \ libc/testlib/waitforterm.c \ - libc/testlib/yield.c + libc/testlib/yield.c \ LIBC_TESTLIB_A_SRCS = \ $(LIBC_TESTLIB_A_SRCS_S) \ @@ -108,6 +112,7 @@ LIBC_TESTLIB_A_DIRECTDEPS = \ LIBC_STR \ LIBC_SYSV \ LIBC_SYSV_CALLS \ + LIBC_THREAD \ LIBC_TINYMATH \ LIBC_X \ THIRD_PARTY_COMPILER_RT \ @@ -210,6 +215,7 @@ LIBC_TESTMAIN_DIRECTDEPS = \ LIBC_LOG \ LIBC_MEM \ LIBC_NEXGEN32E \ + LIBC_PROC \ LIBC_RUNTIME \ LIBC_STDIO \ LIBC_SYSV \ @@ -217,7 +223,7 @@ LIBC_TESTMAIN_DIRECTDEPS = \ LIBC_TESTLIB \ LIBC_TESTLIB_RUNNER \ THIRD_PARTY_DLMALLOC \ - THIRD_PARTY_GETOPT + THIRD_PARTY_GETOPT \ LIBC_TESTMAIN_DEPS := \ $(call uniq,$(foreach x,$(LIBC_TESTMAIN_DIRECTDEPS),$($(x)))) diff --git a/libc/testlib/benchmark.h b/libc/testlib/benchmark.h index e7079509e..8915dfb6e 100644 --- a/libc/testlib/benchmark.h +++ b/libc/testlib/benchmark.h @@ -71,7 +71,7 @@ static void _print_benchmark_result(double total_nanos, double work_per_run, work_unit = " "; } - printf("%8.2f %-2s %8.2f %s/s %6.2f %s %2dx %s\n", time_value, time_unit, + printf("%8.2f %-2s %8.2f %s/s %6.2f %s %3dx %s\n", time_value, time_unit, throughput, throughput_unit, work_per_run, work_unit, iterations, code); } diff --git a/libc/testlib/manystack.c b/libc/testlib/manystack.c new file mode 100644 index 000000000..b0b022ba1 --- /dev/null +++ b/libc/testlib/manystack.c @@ -0,0 +1,69 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 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/manystack.h" +#include "libc/atomic.h" +#include "libc/calls/struct/sigaction.h" +#include "libc/calls/struct/sigset.h" +#include "libc/intrin/dll.h" +#include "libc/sysv/consts/sig.h" +#include "libc/thread/posixthread.internal.h" + +static atomic_int manystack_gotsig; +static atomic_bool manystack_shutdown; + +static void manystack_signal(int sig) { + manystack_gotsig = sig; +} + +static void *manystack_thread(void *arg) { + sigset_t ss; + sigfillset(&ss); + sigdelset(&ss, SIGUSR2); + while (!manystack_shutdown) { + sigsuspend(&ss); + if (!manystack_shutdown) { + _pthread_lock(); + for (struct Dll *e = dll_first(_pthread_list); e; + e = dll_next(_pthread_list, e)) { + pthread_t th = (pthread_t)POSIXTHREAD_CONTAINER(e); + if (!pthread_equal(th, pthread_self())) + pthread_kill(th, SIGQUIT); + } + _pthread_unlock(); + } + } + return 0; +} + +pthread_t manystack_start(void) { + sigset_t ss; + pthread_t msh; + sigemptyset(&ss); + sigaddset(&ss, SIGUSR2); + sigprocmask(SIG_BLOCK, &ss, 0); + signal(SIGUSR2, manystack_signal); + pthread_create(&msh, 0, manystack_thread, 0); + return msh; +} + +void manystack_stop(pthread_t msh) { + manystack_shutdown = true; + pthread_kill(msh, SIGUSR2); + pthread_join(msh, 0); +} diff --git a/libc/testlib/manystack.h b/libc/testlib/manystack.h new file mode 100644 index 000000000..a175ecbea --- /dev/null +++ b/libc/testlib/manystack.h @@ -0,0 +1,10 @@ +#ifndef COSMOPOLITAN_LIBC_TESTLIB_MANYSTACK_H_ +#define COSMOPOLITAN_LIBC_TESTLIB_MANYSTACK_H_ +#include "libc/thread/thread.h" +COSMOPOLITAN_C_START_ + +pthread_t manystack_start(void); +void manystack_stop(pthread_t); + +COSMOPOLITAN_C_END_ +#endif /* COSMOPOLITAN_LIBC_TESTLIB_MANYSTACK_H_ */ diff --git a/libc/testlib/testmain.c b/libc/testlib/testmain.c index 55d34f80f..538ca3ec5 100644 --- a/libc/testlib/testmain.c +++ b/libc/testlib/testmain.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" #include "libc/calls/calls.h" +#include "libc/calls/struct/cpuset.h" #include "libc/calls/struct/rlimit.h" #include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/siginfo.h" @@ -31,6 +32,7 @@ #include "libc/intrin/strace.h" #include "libc/intrin/ubsan.h" #include "libc/intrin/weaken.h" +#include "libc/limits.h" #include "libc/log/log.h" #include "libc/macros.h" #include "libc/mem/leaks.h" @@ -38,9 +40,12 @@ #include "libc/nexgen32e/nexgen32e.h" #include "libc/runtime/runtime.h" #include "libc/runtime/symbols.internal.h" +#include "libc/stdio/rand.h" #include "libc/str/str.h" #include "libc/sysv/consts/f.h" +#include "libc/sysv/consts/map.h" #include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/prot.h" #include "libc/sysv/consts/rlimit.h" #include "libc/sysv/consts/sig.h" #include "libc/testlib/aspect.internal.h" @@ -49,6 +54,8 @@ #include "libc/thread/tls.h" #include "third_party/getopt/getopt.internal.h" +#pragma weak main + #define USAGE \ " [FLAGS]\n\ \n\ @@ -85,7 +92,41 @@ static void GetOpts(int argc, char *argv[]) { } } -#pragma weak main +static int rando(void) { + return _rand64() & INT_MAX; +} + +static void limit_process_to_single_cpu(void) { + extern int disable_limit_process_to_single_cpu; + if (_weaken(disable_limit_process_to_single_cpu)) + return; + if (!(IsLinux() || IsFreebsd() || IsNetbsd() || IsWindows())) + return; + if (IsFreebsd() && getuid()) + return; + cpu_set_t legal; + if (sched_getaffinity(0, sizeof(cpu_set_t), &legal) == -1) { + perror("sched_setaffinity failed"); + exit(1); + } + int count = CPU_COUNT(&legal); + cpu_set_t newset; + CPU_ZERO(&newset); + bool done = false; + while (!done) { + for (int i = 0; i < CPU_SETSIZE; ++i) { + if (CPU_ISSET(i, &legal) && !(rando() % count)) { + CPU_SET(rando() % count, &newset); + done = true; + break; + } + } + } + if (sched_setaffinity(0, sizeof(cpu_set_t), &newset) == -1) { + perror("sched_setaffinity failed"); + exit(1); + } +} /** * Generic test program main function. @@ -95,19 +136,33 @@ int main(int argc, char *argv[]) { struct Dll *e; struct TestAspect *a; + // some settings + __ubsan_strict = true; + __log_level = kLogInfo; + if (errno) { tinyprint(2, "error: the errno variable was contaminated by constructors\n", NULL); return 1; } - __ubsan_strict = true; - __log_level = kLogInfo; + // // this sometimes helps tease out mt bugs + // limit_process_to_single_cpu(); + + // test huge pointers by enabling pml5t + if (rando() % 2) { + errno_t e = errno; + mmap((char *)0x80000000000000, 1, PROT_NONE, // + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + errno = e; + } + GetOpts(argc, argv); - for (fd = 3; fd < 100; ++fd) { + int oe = errno; + for (fd = 3; fd < 100; ++fd) close(fd); - } + errno = oe; #ifndef TINY setenv("GDB", "", true); @@ -156,13 +211,14 @@ int main(int argc, char *argv[]) { // make sure threads are in a good state if (_weaken(_pthread_decimate)) - _weaken(_pthread_decimate)(false); + _weaken(_pthread_decimate)(kPosixThreadZombie); if (_weaken(pthread_orphan_np) && !_weaken(pthread_orphan_np)()) { tinyprint(2, "error: tests ended with threads still active\n", NULL); _Exit(1); } // check for memory leaks + AssertNoLocksAreHeld(); if (!g_testlib_failed) CheckForMemoryLeaks(); diff --git a/libc/testlib/testrunner.c b/libc/testlib/testrunner.c index 27ef2a639..b8498e1eb 100644 --- a/libc/testlib/testrunner.c +++ b/libc/testlib/testrunner.c @@ -34,6 +34,7 @@ #include "libc/str/str.h" #include "libc/testlib/aspect.internal.h" #include "libc/testlib/testlib.h" +#include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" #include "libc/x/x.h" @@ -52,7 +53,7 @@ void testlib_finish(void) { void testlib_error_enter(const char *file, const char *func) { ftrace_enabled(-1); strace_enabled(-1); - pthread_mutex_lock(&testlib_error_lock); + _pthread_mutex_lock(&testlib_error_lock); if (!IsWindows()) sys_getpid(); /* make strace easier to read */ if (!IsWindows()) @@ -67,7 +68,7 @@ void testlib_error_enter(const char *file, const char *func) { void testlib_error_leave(void) { strace_enabled(+1); ftrace_enabled(+1); - pthread_mutex_unlock(&testlib_error_lock); + _pthread_mutex_unlock(&testlib_error_lock); } wontreturn void testlib_abort(void) { diff --git a/libc/testlib/trace.c b/libc/testlib/trace.c new file mode 100644 index 000000000..ca9b753c8 --- /dev/null +++ b/libc/testlib/trace.c @@ -0,0 +1,151 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 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 "trace.h" +#include +#include +#include +#include +#include +#include + +struct TraceEvent { + unsigned long long ts; + int pid; + int tid; + const char* name; + const char* cat; + char ph; +}; + +static int g_pid; +static atomic_bool g_oom; +static atomic_int g_count; +static thread_local int g_id; +static thread_local int g_ids; +static thread_local int g_tid; +static unsigned long g_start_rdtsc; +static struct TraceEvent g_events[1000000]; + +static unsigned long rdtsc(void) { +#ifdef __x86_64__ + unsigned ax, dx; + __asm__ volatile("rdtsc" : "=a"(ax), "=d"(dx)); + return (unsigned long)dx << 32 | ax; +#else + unsigned long c; + __asm__ volatile("mrs %0, cntvct_el0" : "=r"(c)); + return c * 48; // the fudge factor +#endif +} + +static int cosmo_trace_oom(void) { + if (atomic_load_explicit(&g_oom, memory_order_relaxed)) + return -1; + if (atomic_exchange_explicit(&g_oom, true, memory_order_acq_rel)) + return -1; + fprintf(stderr, "warning: ran out of trace event memory\n"); + return -1; +} + +static int cosmo_trace_reserve(int count) { + int id = atomic_load_explicit(&g_count, memory_order_relaxed); + if (id + count > sizeof(g_events) / sizeof(*g_events)) + return cosmo_trace_oom(); + id = atomic_fetch_add_explicit(&g_count, count, memory_order_acq_rel); + if (id + count > sizeof(g_events) / sizeof(*g_events)) + return cosmo_trace_oom(); + return id; +} + +static void cosmo_trace_event(int id, const char* name, const char* cat, + char ph) { + g_events[id].ts = rdtsc(); + g_events[id].pid = g_pid ? g_pid - 1 : getpid(); + g_events[id].tid = g_tid ? g_tid - 1 : gettid(); + g_events[id].name = name; + g_events[id].cat = cat; + g_events[id].ph = ph; +} + +void cosmo_trace_set_pid(int pid) { + g_pid = pid + 1; +} + +void cosmo_trace_set_tid(int tid) { + g_tid = tid + 1; +} + +void cosmo_trace_begin(const char* name) { + if (g_ids < 2) { + g_ids = 20; + g_id = cosmo_trace_reserve(g_ids); + if (g_id == -1) { + g_ids = 0; + return; + } + } + cosmo_trace_event(g_id++, name, "category", 'B'); + --g_ids; +} + +void cosmo_trace_end(const char* name) { + if (g_ids < 1) + return; + cosmo_trace_event(g_id++, name, "category", 'E'); + --g_ids; +} + +static void cosmo_trace_save(const char* filename) { + int count = atomic_load_explicit(&g_count, memory_order_relaxed); + if (!count) + return; + fprintf(stderr, "saving trace to %s...\n", filename); + FILE* file = fopen(filename, "w"); + if (!file) { + perror(filename); + return; + } + fprintf(file, "[\n"); + bool once = false; + for (int i = 0; i < count; i++) { + if (!g_events[i].name) + continue; + if (!once) { + once = true; + } else { + fputs(",\n", file); + } + fprintf(file, + "{\"name\": \"%s\", \"cat\": \"%s\", \"ph\": \"%c\", " + "\"ts\": %.3f, \"pid\": %d, \"tid\": %d}", + g_events[i].name, g_events[i].cat, g_events[i].ph, + (g_events[i].ts - g_start_rdtsc) / 3000., g_events[i].pid, + g_events[i].tid); + } + fprintf(file, "\n]\n"); + fclose(file); +} + +__attribute__((__constructor__)) static void trace_startup(void) { + g_start_rdtsc = rdtsc(); +} + +__attribute__((__destructor__)) static void trace_shutdown(void) { + cosmo_trace_save("trace.json"); // see chrome://tracing/ +} diff --git a/libc/testlib/trace.h b/libc/testlib/trace.h new file mode 100644 index 000000000..05d438ff5 --- /dev/null +++ b/libc/testlib/trace.h @@ -0,0 +1,11 @@ +#ifndef COSMOPOLITAN_LIBC_TESTLIB_TRACE_H_ +#define COSMOPOLITAN_LIBC_TESTLIB_TRACE_H_ +COSMOPOLITAN_C_START_ + +void cosmo_trace_set_pid(int); +void cosmo_trace_set_tid(int); +void cosmo_trace_begin(const char*); +void cosmo_trace_end(const char*); + +COSMOPOLITAN_C_END_ +#endif /* COSMOPOLITAN_LIBC_TESTLIB_TRACE_H_ */ diff --git a/libc/runtime/isstackoverflow.c b/libc/thread/isstackoverflow.c similarity index 76% rename from libc/runtime/isstackoverflow.c rename to libc/thread/isstackoverflow.c index 35c646dd9..850eb5a60 100644 --- a/libc/runtime/isstackoverflow.c +++ b/libc/thread/isstackoverflow.c @@ -23,17 +23,36 @@ #include "libc/runtime/runtime.h" #include "libc/sysv/consts/auxv.h" #include "libc/sysv/consts/sig.h" +#include "libc/thread/thread.h" /** - * Returns true if signal is most likely a stack overflow. + * Returns true if signal is caused by stack overflow. */ char __is_stack_overflow(siginfo_t *si, void *arg) { + + // sanity check ucontext_t *uc = arg; if (!si || !uc) return false; - if (si->si_signo != SIGSEGV && si->si_signo != SIGBUS) + if (si->si_signo != SIGSEGV && // + si->si_signo != SIGBUS) return false; - intptr_t sp = uc->uc_mcontext.SP; - intptr_t fp = (intptr_t)si->si_addr; - return ABS(fp - sp) < __pagesize; + + // get stack information + pthread_attr_t attr; + if (pthread_getattr_np(pthread_self(), &attr)) + return false; + size_t guardsize; + if (pthread_attr_getguardsize(&attr, &guardsize)) + return false; + void *stackaddr; + size_t stacksize; + if (pthread_attr_getstack(&attr, &stackaddr, &stacksize)) + return false; + + // determine if faulting address is inside guard region + char *x = (char *)si->si_addr; + char *lo = (char *)stackaddr - guardsize; + char *hi = (char *)stackaddr; + return lo <= x && x < hi; } diff --git a/libc/thread/itimer.c b/libc/thread/itimer.c index 15db1893d..fd93cf00d 100644 --- a/libc/thread/itimer.c +++ b/libc/thread/itimer.c @@ -28,31 +28,34 @@ #include "libc/intrin/strace.h" #include "libc/nt/enum/processcreationflags.h" #include "libc/nt/thread.h" +#include "libc/runtime/runtime.h" #include "libc/str/str.h" #include "libc/sysv/consts/clock.h" +#include "libc/sysv/consts/map.h" +#include "libc/sysv/consts/prot.h" #include "libc/sysv/consts/sicode.h" #include "libc/sysv/consts/sig.h" #include "libc/sysv/errfuns.h" -#include "libc/thread/itimer.internal.h" +#include "libc/thread/itimer.h" +#include "libc/thread/posixthread.internal.h" +#include "libc/thread/thread2.h" #include "libc/thread/tls.h" -#include "third_party/nsync/mu.h" #ifdef __x86_64__ #define STACK_SIZE 65536 -struct IntervalTimer __itimer; - -static textwindows dontinstrument uint32_t __itimer_worker(void *arg) { +textwindows dontinstrument static uint32_t __itimer_worker(void *arg) { struct CosmoTib tls; char *sp = __builtin_frame_address(0); __bootstrap_tls(&tls, sp); __maps_track( (char *)(((uintptr_t)sp + __pagesize - 1) & -__pagesize) - STACK_SIZE, - STACK_SIZE); + STACK_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_NOFORK); for (;;) { bool dosignal = false; struct timeval now, waituntil; - nsync_mu_lock(&__itimer.lock); + __itimer_lock(); now = timeval_real(); if (timeval_iszero(__itimer.it.it_value)) { waituntil = timeval_max; @@ -73,55 +76,46 @@ static textwindows dontinstrument uint32_t __itimer_worker(void *arg) { dosignal = true; } } - nsync_mu_unlock(&__itimer.lock); + __itimer_unlock(); if (dosignal) __sig_generate(SIGALRM, SI_TIMER); - nsync_mu_lock(&__itimer.lock); - nsync_cv_wait_with_deadline(&__itimer.cond, &__itimer.lock, CLOCK_REALTIME, - timeval_totimespec(waituntil), 0); - nsync_mu_unlock(&__itimer.lock); + __itimer_lock(); + struct timespec deadline = timeval_totimespec(waituntil); + _pthread_cond_timedwait(&__itimer.cond, &__itimer.lock, &deadline); + __itimer_unlock(); } return 0; } -static textwindows void __itimer_setup(void) { +textwindows static void __itimer_setup(void) { __itimer.thread = CreateThread(0, STACK_SIZE, __itimer_worker, 0, kNtStackSizeParamIsAReservation, 0); } -textwindows void __itimer_wipe(void) { - // this function is called by fork(), because - // timers aren't inherited by forked subprocesses - bzero(&__itimer, sizeof(__itimer)); -} - textwindows int sys_setitimer_nt(int which, const struct itimerval *neu, struct itimerval *old) { struct itimerval config; cosmo_once(&__itimer.once, __itimer_setup); if (which != ITIMER_REAL || (neu && (!timeval_isvalid(neu->it_value) || - !timeval_isvalid(neu->it_interval)))) { + !timeval_isvalid(neu->it_interval)))) return einval(); - } - if (neu) { + if (neu) // POSIX defines setitimer() with the restrict keyword but let's // accommodate the usage setitimer(ITIMER_REAL, &it, &it) anyway config = *neu; - } BLOCK_SIGNALS; - nsync_mu_lock(&__itimer.lock); + __itimer_lock(); if (old) { old->it_interval = __itimer.it.it_interval; old->it_value = timeval_subz(__itimer.it.it_value, timeval_real()); } if (neu) { - if (!timeval_iszero(config.it_value)) { + if (!timeval_iszero(config.it_value)) config.it_value = timeval_add(config.it_value, timeval_real()); - } __itimer.it = config; - nsync_cv_signal(&__itimer.cond); + _pthread_cond_signal(&__itimer.cond); } - nsync_mu_unlock(&__itimer.lock); + __itimer_unlock(); ALLOW_SIGNALS; return 0; } diff --git a/libc/thread/itimer.internal.h b/libc/thread/itimer.h similarity index 67% rename from libc/thread/itimer.internal.h rename to libc/thread/itimer.h index 41d721216..a5193d987 100644 --- a/libc/thread/itimer.internal.h +++ b/libc/thread/itimer.h @@ -2,21 +2,22 @@ #define COSMOPOLITAN_LIBC_ITIMER_H_ #include "libc/atomic.h" #include "libc/calls/struct/itimerval.h" -#include "third_party/nsync/cv.h" -#include "third_party/nsync/mu.h" +#include "libc/thread/thread.h" COSMOPOLITAN_C_START_ struct IntervalTimer { atomic_uint once; intptr_t thread; - nsync_mu lock; - nsync_cv cond; + pthread_mutex_t lock; + pthread_cond_t cond; struct itimerval it; }; extern struct IntervalTimer __itimer; -void __itimer_wipe(void); +void __itimer_lock(void); +void __itimer_unlock(void); +void __itimer_wipe_and_reset(void); COSMOPOLITAN_C_END_ #endif /* COSMOPOLITAN_LIBC_ITIMER_H_ */ diff --git a/libc/thread/lock.h b/libc/thread/lock.h index 2947da75f..75b0177a2 100644 --- a/libc/thread/lock.h +++ b/libc/thread/lock.h @@ -3,18 +3,25 @@ COSMOPOLITAN_C_START_ // -// ┌depth -// │ -// COSMOPOLITAN MUTEXES │ ┌waited -// │ │ -// │ │┌locked -// │ ││ -// │ ││┌pshared -// owner │ │││ -// tid │ │││┌type -// │ │ ││││ -// ┌──────────────┴───────────────┐ ┌─┴──┐│││├┐ +// ┌undead +// │ +// │┌dead +// ││ +// ││┌robust +// │││ +// │││ ┌depth +// │││ │ +// COSMOPOLITAN MUTEXES │││ │ ┌waited +// │││ │ │ +// │││ │ │┌locked +// │││ │ ││ +// │││ │ ││┌pshared +// owner │││ │ │││ +// tid │││ │ │││┌type +// │ │││ │ ││││ +// ┌──────────────┴───────────────┐ │││┌─┴──┐│││├┐ // 0b0000000000000000000000000000000000000000000000000000000000000000 +// #define MUTEX_DEPTH_MIN 0x00000020ull #define MUTEX_DEPTH_MAX 0x000007e0ull diff --git a/libc/runtime/mapstack.c b/libc/thread/mapstack.c similarity index 70% rename from libc/runtime/mapstack.c rename to libc/thread/mapstack.c index eccd5cefc..470ab58a6 100644 --- a/libc/runtime/mapstack.c +++ b/libc/thread/mapstack.c @@ -16,18 +16,9 @@ │ 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/dce.h" -#include "libc/runtime/memtrack.internal.h" -#include "libc/runtime/runtime.h" +#include "libc/cosmo.h" +#include "libc/errno.h" #include "libc/runtime/stack.h" -#include "libc/sysv/consts/auxv.h" -#include "libc/sysv/consts/map.h" -#include "libc/sysv/consts/prot.h" - -#define MAP_ANON_OPENBSD 0x1000 -#define MAP_STACK_OPENBSD 0x4000 /** * Allocates stack. @@ -43,28 +34,23 @@ * @return stack bottom address on success, or null w/ errno */ void *NewCosmoStack(void) { - char *p; - size_t n = GetStackSize(); - if ((p = mmap(0, n, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, - 0)) != MAP_FAILED) { - if (IsOpenbsd() && __sys_mmap(p, n, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_FIXED | MAP_ANON_OPENBSD | - MAP_STACK_OPENBSD, - -1, 0, 0) != p) - notpossible; - if (mprotect(p, GetGuardSize(), PROT_NONE | PROT_GUARD)) - notpossible; - return p; - } else { - return 0; - } + void *stackaddr; + size_t stacksize = GetStackSize(); + size_t guardsize = GetGuardSize(); + errno_t err = cosmo_stack_alloc(&stacksize, &guardsize, &stackaddr); + if (!err) + return stackaddr; + errno = err; + return 0; } /** * Frees stack. * - * @param stk was allocated by NewCosmoStack() + * @param stackaddr was allocated by NewCosmoStack() + * @return 0 on success, or -1 w/ errno */ -int FreeCosmoStack(void *stk) { - return munmap(stk, GetStackSize()); +int FreeCosmoStack(void *stackaddr) { + cosmo_stack_free(stackaddr, GetStackSize(), GetGuardSize()); + return 0; } diff --git a/libc/thread/mktls.c b/libc/thread/mktls.c index b48ea3137..20d574b93 100644 --- a/libc/thread/mktls.c +++ b/libc/thread/mktls.c @@ -40,10 +40,9 @@ static char *_mktls_finish(struct CosmoTib **out_tib, char *mem, tib->tib_ftrace = old->tib_ftrace; tib->tib_strace = old->tib_strace; tib->tib_sigmask = old->tib_sigmask; - atomic_store_explicit(&tib->tib_tid, -1, memory_order_relaxed); - if (out_tib) { + atomic_init(&tib->tib_ctid, -1); + if (out_tib) *out_tib = tib; - } return mem; } diff --git a/libc/thread/posixthread.internal.h b/libc/thread/posixthread.internal.h index 40c84330d..2a4ca4c19 100644 --- a/libc/thread/posixthread.internal.h +++ b/libc/thread/posixthread.internal.h @@ -74,50 +74,61 @@ struct PosixThread { int pt_flags; // 0x00: see PT_* constants atomic_int pt_canceled; // 0x04: thread has bad beliefs _Atomic(enum PosixThreadStatus) pt_status; - atomic_int ptid; // transitions 0 → tid + _Atomic(atomic_int *) pt_blocker; atomic_int pt_refs; // prevents decimation void *(*pt_start)(void *); // creation callback - void *pt_arg; // start's parameter - void *pt_rc; // start's return value + void *pt_val; // start param / return val char *pt_tls; // bottom of tls allocation struct CosmoTib *tib; // middle of tls allocation struct Dll list; // list of threads struct _pthread_cleanup_buffer *pt_cleanup; - _Atomic(atomic_int *) pt_blocker; uint64_t pt_blkmask; int64_t pt_event; locale_t pt_locale; - jmp_buf pt_exiter; + intptr_t pt_exiter[5]; pthread_attr_t pt_attr; + atomic_bool pt_intoff; }; typedef void (*atfork_f)(void); extern struct Dll *_pthread_list; +extern atomic_uint _pthread_count; extern struct PosixThread _pthread_static; extern _Atomic(pthread_key_dtor) _pthread_key_dtor[PTHREAD_KEYS_MAX]; -int _pthread_atfork(atfork_f, atfork_f, atfork_f) libcesque; +int _pthread_cond_signal(pthread_cond_t *) dontthrow paramsnonnull(); +int _pthread_mutex_lock(pthread_mutex_t *) dontthrow paramsnonnull(); +int _pthread_mutex_trylock(pthread_mutex_t *) dontthrow paramsnonnull(); +int _pthread_mutex_unlock(pthread_mutex_t *) dontthrow paramsnonnull(); +int _pthread_mutex_wipe_np(pthread_mutex_t *) libcesque paramsnonnull(); int _pthread_reschedule(struct PosixThread *) libcesque; int _pthread_setschedparam_freebsd(int, int, const struct sched_param *); int _pthread_tid(struct PosixThread *) libcesque; intptr_t _pthread_syshand(struct PosixThread *) libcesque; long _pthread_cancel_ack(void) libcesque; -void _pthread_decimate(bool) libcesque; -void _pthread_free(struct PosixThread *) libcesque; -void _pthread_lock(void) libcesque; -void _pthread_onfork_child(void) libcesque; -void _pthread_onfork_parent(void) libcesque; -void _pthread_onfork_prepare(void) libcesque; -void _pthread_unlock(void) libcesque; -void _pthread_zombify(struct PosixThread *) libcesque; +void _pthread_decimate(enum PosixThreadStatus) dontthrow; +void _pthread_free(struct PosixThread *) libcesque paramsnonnull(); +void _pthread_lock(void) dontthrow; +void _pthread_onfork_child(void) dontthrow; +void _pthread_onfork_parent(void) dontthrow; +void _pthread_onfork_prepare(void) dontthrow; +void _pthread_unlock(void) dontthrow; +void _pthread_zombify(struct PosixThread *) dontthrow; + +int _pthread_cond_wait(pthread_cond_t *, pthread_mutex_t *) dontthrow + paramsnonnull(); + +int _pthread_cond_timedwait(pthread_cond_t *, pthread_mutex_t *, + const struct timespec *) dontthrow + paramsnonnull((1, 2)); forceinline pureconst struct PosixThread *_pthread_self(void) { return (struct PosixThread *)__get_tls()->tib_pthread; } forceinline void _pthread_ref(struct PosixThread *pt) { - atomic_fetch_add_explicit(&pt->pt_refs, 1, memory_order_acq_rel); + atomic_fetch_add_explicit(&pt->pt_refs, 1, memory_order_relaxed); } forceinline void _pthread_unref(struct PosixThread *pt) { diff --git a/libc/thread/pthread_atfork.c b/libc/thread/pthread_atfork.c new file mode 100644 index 000000000..ec8cc05fc --- /dev/null +++ b/libc/thread/pthread_atfork.c @@ -0,0 +1,182 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et 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/atomic.h" +#include "libc/cosmo.h" +#include "libc/errno.h" +#include "libc/intrin/strace.h" +#include "libc/mem/mem.h" +#include "libc/thread/posixthread.internal.h" +#include "libc/thread/thread.h" + +struct AtFork { + struct AtFork *p[2]; + atfork_f f[3]; +}; + +struct AtForks { + atomic_uint once; + pthread_mutex_t lock; + struct AtFork *list; +}; + +static struct AtForks _atforks = { + .lock = PTHREAD_MUTEX_INITIALIZER, +}; + +static void pthread_atfork_clear(void) { + struct AtFork *a, *b; + for (a = _atforks.list; a; a = b) { + b = a->p[0]; + free(a); + } +} + +static void pthread_atfork_init(void) { + atexit(pthread_atfork_clear); +} + +static void _pthread_onfork(int i, const char *op) { + struct AtFork *a; + for (a = _atforks.list; a; a = a->p[!i]) { + if (a->f[i]) { + STRACE("pthread_atfork(%s, %t)", op, a->f[i]); + a->f[i](); + } + _atforks.list = a; + } +} + +void _pthread_onfork_prepare(void) { + _pthread_mutex_lock(&_atforks.lock); + _pthread_onfork(0, "prepare"); +} + +void _pthread_onfork_parent(void) { + _pthread_onfork(1, "parent"); + _pthread_mutex_unlock(&_atforks.lock); +} + +void _pthread_onfork_child(void) { + _pthread_mutex_wipe_np(&_atforks.lock); + _pthread_onfork(2, "child"); +} + +/** + * Registers fork callbacks. + * + * When fork happens, your prepare functions will be called in the + * reverse order they were registered. Then, in the parent and child + * processes, their callbacks will be called in the same order they were + * registered. + * + * One big caveat with fork() is that it hard kills all threads except + * the calling thread. So let's say one of those threads was printing to + * stdout while it was killed. In that case, the stdout lock will still + * be held when your child process comes alive, which means that the + * child will deadlock next time it tries to print. + * + * The solution for that is simple. Every lock in your process should be + * registered with this interface. However there's one highly important + * thing you need to know. Locks must follow a consistent hierarchy. So + * the order in which you register locks matters. If nested locks aren't + * acquired in the same order globally, then rarely occurring deadlocks + * will happen. So what we recommend is that you hunt down all the locks + * that exist in your app and its dependencies, and register them all at + * once from your main() function at startup. This ensures a clear order + * and if you aren't sure what that order should be, cosmo libc has got + * you covered. Simply link your program with the `cosmocc -mdbg` flag + * and cosmo will detect locking violations with your `pthread_mutex_t` + * objects and report them by printing the strongly connected component. + * This will include the demangled symbol name of each mutex, assuming + * the `pthread_mutex_t` objects are stored in static memory. cosmo.h + * also exposes a deadlock API that lets you incorporate your own lock + * object types into this error checking system, which we also use to + * verify the entire libc runtime itself. See libc/intrin/deadlock.c. + * + * Special care should be taken when using this interface in libraries. + * While it may seem tempting to use something like a `__constructor__` + * attribute to register your mutexes in a clean and abstracted way, it + * is only appropriate if your mutex is guarding pure memory operations + * and poses zero risk of nesting with locks outside your library. For + * example, calling open() or printf() while holding your lock will do + * just that, since the C runtime functions you may consider pure will + * actually use mutexes under the hood, which are also validated under + * `cosmocc -mdbg` builds. So if your locks can't be made unnestable + * pure memory operations, then you should consider revealing their + * existence to users of your library. + * + * Here's an example of how pthread_atfork() can be used: + * + * static struct { + * pthread_once_t once; + * pthread_mutex_t lock; + * // data structures... + * } g_lib; + * + * static void lib_lock(void) { + * pthread_mutex_lock(&g_lib.lock); + * } + * + * static void lib_unlock(void) { + * pthread_mutex_unlock(&g_lib.lock); + * } + * + * static void lib_wipe(void) { + * pthread_mutex_wipe_np(&g_lib.lock); + * } + * + * static void lib_setup(void) { + * pthread_mutex_init(&g_lib.lock, 0); + * pthread_atfork(lib_lock, lib_unlock, lib_wipe); + * } + * + * static void lib_init(void) { + * pthread_once(&g_lib.once, lib_setup); + * } + * + * void lib(void) { + * lib_init(); + * lib_lock(); + * // do stuff... + * lib_unlock(); + * } + * + * @param prepare is run by fork() before forking happens + * @param parent is run by fork() after forking happens in parent process + * @param child is run by fork() after forking happens in childe process + * @return 0 on success, or errno on error + * @raise ENOMEM if we require more vespene gas + */ +int pthread_atfork(atfork_f prepare, atfork_f parent, atfork_f child) { + cosmo_once(&_atforks.once, pthread_atfork_init); + struct AtFork *a; + if (!(a = calloc(1, sizeof(struct AtFork)))) + return ENOMEM; + a->f[0] = prepare; + a->f[1] = parent; + a->f[2] = child; + _pthread_mutex_lock(&_atforks.lock); + a->p[0] = 0; + a->p[1] = _atforks.list; + if (_atforks.list) + _atforks.list->p[0] = a; + _atforks.list = a; + _pthread_mutex_unlock(&_atforks.lock); + return 0; +} diff --git a/libc/thread/pthread_attr_getguardsize.c b/libc/thread/pthread_attr_getguardsize.c index fd4524efb..ba10c3014 100644 --- a/libc/thread/pthread_attr_getguardsize.c +++ b/libc/thread/pthread_attr_getguardsize.c @@ -19,7 +19,7 @@ #include "libc/thread/thread.h" /** - * Returns size of protected region at bottom of thread stack. + * Returns size of protected region beneath thread stack. * * @param guardsize will be set to guard size in bytes * @return 0 on success, or errno on error diff --git a/libc/thread/pthread_attr_getstack.c b/libc/thread/pthread_attr_getstack.c index 8b9a9c06d..27c744d81 100644 --- a/libc/thread/pthread_attr_getstack.c +++ b/libc/thread/pthread_attr_getstack.c @@ -20,15 +20,13 @@ #include "libc/thread/thread.h" /** - * Returns configuration for thread stack. + * Returns configuration for custom thread stack. * - * This is a getter for a configuration attribute. By default, zeros are - * returned. If pthread_attr_setstack() was called earlier, then this'll - * return those earlier supplied values. + * If zero is returned to `*stackaddr` then a custom stack hasn't been + * specified by a previous call to pthread_attr_setstack(). * * @param stackaddr will be set to stack address in bytes * @return 0 on success, or errno on error - * @see pthread_attr_setstacksize() */ errno_t pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr, size_t *stacksize) { diff --git a/libc/thread/pthread_attr_init.c b/libc/thread/pthread_attr_init.c index ec5fa47b1..4ef1e9207 100644 --- a/libc/thread/pthread_attr_init.c +++ b/libc/thread/pthread_attr_init.c @@ -40,7 +40,7 @@ errno_t pthread_attr_init(pthread_attr_t *attr) { *attr = (pthread_attr_t){ .__stacksize = GetStackSize(), - .__guardsize = __pagesize, + .__guardsize = GetGuardSize(), }; return 0; } diff --git a/libc/thread/pthread_attr_setdetachstate.c b/libc/thread/pthread_attr_setdetachstate.c index 253f04495..e9a57a084 100644 --- a/libc/thread/pthread_attr_setdetachstate.c +++ b/libc/thread/pthread_attr_setdetachstate.c @@ -28,6 +28,10 @@ * pthread_create(0, &attr, func, 0); * pthread_attr_destroy(&attr); * + * If you use this, please be warned that your thread might run and exit + * before pthread_create() even returns. You really should assume it can + * not be used with any pthread APIs from the calling thread. + * * @param detachstate can be one of * - `PTHREAD_CREATE_JOINABLE` (default) * - `PTHREAD_CREATE_DETACHED` diff --git a/libc/thread/pthread_attr_setguardsize.c b/libc/thread/pthread_attr_setguardsize.c index e404ea04f..4b776cdd9 100644 --- a/libc/thread/pthread_attr_setguardsize.c +++ b/libc/thread/pthread_attr_setguardsize.c @@ -19,13 +19,7 @@ #include "libc/thread/thread.h" /** - * Sets size of protected region at bottom of thread stack. - * - * Cosmopolitan sets this value to `sysconf(_SC_PAGESIZE)` by default. - * - * You may set `guardsize` to disable the stack guard feature and gain a - * slight performance advantage by avoiding mprotect() calls. Note that - * it could make your code more prone to silent unreported corruption. + * Sets minimum size of protected region beneath thread stack. * * @param guardsize contains guard size in bytes, which is implicitly * rounded up to `sysconf(_SC_PAGESIZE)`, or zero to disable diff --git a/libc/thread/pthread_attr_setsigmask_np.c b/libc/thread/pthread_attr_setsigmask_np.c index b46c94e57..a42e8b055 100644 --- a/libc/thread/pthread_attr_setsigmask_np.c +++ b/libc/thread/pthread_attr_setsigmask_np.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/str/str.h" +#include "libc/sysv/consts/sig.h" #include "libc/thread/thread2.h" /** diff --git a/libc/thread/pthread_attr_setstack.c b/libc/thread/pthread_attr_setstack.c index 8bfaed866..9017362af 100644 --- a/libc/thread/pthread_attr_setstack.c +++ b/libc/thread/pthread_attr_setstack.c @@ -16,64 +16,42 @@ │ 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/limits.h" +#include "libc/runtime/stack.h" #include "libc/thread/thread.h" /** - * Configures custom allocated stack for thread, e.g. + * Configures custom stack for thread. * - * pthread_t id; - * pthread_attr_t attr; - * char *stk = NewCosmoStack(); - * pthread_attr_init(&attr); - * pthread_attr_setstack(&attr, stk, GetStackSize()); - * pthread_create(&id, &attr, func, 0); - * pthread_attr_destroy(&attr); - * pthread_join(id, 0); - * FreeCosmoStack(stk); + * Normally you want to use pthread_attr_setstacksize() and + * pthread_attr_setguardsize() to configure how pthread_create() + * allocates stack memory for newly created threads. Cosmopolitan is + * very good at managing stack memory. However if you still want to + * allocate stack memory on your own, POSIX defines this function. * - * Your stack must have at least `PTHREAD_STACK_MIN` bytes, which - * Cosmpolitan Libc defines as `GetStackSize()`. It's a link-time - * constant used by Actually Portable Executable that's 128 kb by - * default. See libc/runtime/stack.h for docs on your stack limit - * since the APE ELF phdrs are the one true source of truth here. + * Your `stackaddr` points to the byte at the very bottom of your stack. + * You are responsible for this memory. Your POSIX threads runtime will + * not free or unmap this allocation when the thread has terminated. If + * `stackaddr` is null then `stacksize` is ignored and default behavior + * is restored, i.e. pthread_create() will manage stack allocations. * - * Cosmpolitan Libc runtime magic (e.g. ftrace) and memory safety - * (e.g. kprintf) assumes that stack sizes are two-powers and are - * aligned to that two-power. Conformance isn't required since we - * say caveat emptor to those who don't maintain these invariants - * please consider using NewCosmoStack(), which is always perfect - * or use `mmap(0, GetStackSize() << 1, ...)` for a bigger stack. + * Your `stackaddr` could be created by malloc(). On OpenBSD, + * pthread_create() will augment your custom allocation so it's + * permissable by the kernel to use as a stack. You may also call + * Cosmopolitan APIs such NewCosmoStack() and cosmo_stack_alloc(). + * Static memory can be used, but it won't reduce pthread footprint. * - * Unlike pthread_attr_setstacksize(), this function permits just - * about any parameters and will change the values and allocation - * as needed to conform to the mandatory requirements of the host - * operating system even if it doesn't meet the stricter needs of - * Cosmopolitan Libc userspace libraries. For example with malloc - * allocations, things like page size alignment, shall be handled - * automatically for compatibility with existing codebases. - * - * The same stack shouldn't be used for two separate threads. Use - * fresh stacks for each thread so that ASAN can be much happier. - * - * @param stackaddr is address of stack allocated by caller, and - * may be NULL in which case default behavior is restored - * @param stacksize is size of caller allocated stack * @return 0 on success, or errno on error - * @raise EINVAL if parameters were unacceptable + * @raise EINVAL if `stacksize` is less than `PTHREAD_STACK_MIN` * @see pthread_attr_setstacksize() */ errno_t pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize) { if (!stackaddr) { attr->__stackaddr = 0; - attr->__stacksize = 0; + attr->__stacksize = GetStackSize(); return 0; } - if (stacksize > INT_MAX) - return EINVAL; if (stacksize < PTHREAD_STACK_MIN) return EINVAL; attr->__stackaddr = stackaddr; diff --git a/libc/thread/pthread_attr_setstacksize.c b/libc/thread/pthread_attr_setstacksize.c index 58e69eb15..217b62fad 100644 --- a/libc/thread/pthread_attr_setstacksize.c +++ b/libc/thread/pthread_attr_setstacksize.c @@ -17,19 +17,36 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/errno.h" -#include "libc/limits.h" #include "libc/thread/thread.h" /** - * Defines minimum stack size for thread. + * Specifies minimum stack size for thread, e.g. + * + * pthread_t th; + * pthread_attr_t attr; + * pthread_attr_init(&attr); + * pthread_attr_setguardsize(&attr, 4096); + * pthread_attr_setstacksize(&attr, 61440); + * pthread_create(&th, &attr, thfunc, arg); + * pthread_attr_destroy(&attr); + * + * On Linux, if you're not using `cosmocc -mtiny`, and you're not using + * cosmo_dlopen(), and guard size is nonzero, then `MAP_GROWSDOWN` will + * be used to create your stack memory. This helps minimize virtual + * memory consumption. Please note this is only possible if `stacksize` + * is no larger than the current `RLIMIT_STACK`, otherwise the runtime + * will map your stack using plain old mmap(). + * + * Non-custom stacks may be recycled by the cosmo runtime. You can + * control this behavior by calling cosmo_stack_setmaxstacks(). It's + * useful for both tuning performance and hardening security. See also + * pthread_attr_setguardsize() which is important for security too. * * @param stacksize contains stack size in bytes * @return 0 on success, or errno on error * @raise EINVAL if `stacksize` is less than `PTHREAD_STACK_MIN` */ errno_t pthread_attr_setstacksize(pthread_attr_t *a, size_t stacksize) { - if (stacksize > INT_MAX) - return EINVAL; if (stacksize < PTHREAD_STACK_MIN) return EINVAL; a->__stacksize = stacksize; diff --git a/libc/thread/pthread_barrier_wait.c b/libc/thread/pthread_barrier_wait.c index a4d44bf5e..5a318feed 100644 --- a/libc/thread/pthread_barrier_wait.c +++ b/libc/thread/pthread_barrier_wait.c @@ -17,11 +17,11 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/blockcancel.internal.h" +#include "libc/cosmo.h" #include "libc/errno.h" #include "libc/intrin/atomic.h" #include "libc/limits.h" #include "libc/thread/thread.h" -#include "third_party/nsync/futex.internal.h" /** * Waits for all threads to arrive at barrier. @@ -54,14 +54,14 @@ errno_t pthread_barrier_wait(pthread_barrier_t *barrier) { atomic_store_explicit(&barrier->_counter, barrier->_count, memory_order_release); atomic_store_explicit(&barrier->_waiters, 0, memory_order_release); - nsync_futex_wake_(&barrier->_waiters, INT_MAX, barrier->_pshared); + cosmo_futex_wake(&barrier->_waiters, INT_MAX, barrier->_pshared); return PTHREAD_BARRIER_SERIAL_THREAD; } // wait for everyone else to arrive at barrier BLOCK_CANCELATION; while ((n = atomic_load_explicit(&barrier->_waiters, memory_order_acquire))) - nsync_futex_wait_(&barrier->_waiters, n, barrier->_pshared, 0, 0); + cosmo_futex_wait(&barrier->_waiters, n, barrier->_pshared, 0, 0); ALLOW_CANCELATION; return 0; diff --git a/libc/thread/pthread_cond_broadcast.c b/libc/thread/pthread_cond_broadcast.c index 236d476c8..894a76fb4 100644 --- a/libc/thread/pthread_cond_broadcast.c +++ b/libc/thread/pthread_cond_broadcast.c @@ -16,12 +16,12 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/cosmo.h" #include "libc/dce.h" #include "libc/intrin/atomic.h" #include "libc/limits.h" #include "libc/thread/thread.h" #include "third_party/nsync/cv.h" -#include "third_party/nsync/futex.internal.h" __static_yoink("nsync_mu_lock"); __static_yoink("nsync_mu_unlock"); @@ -55,7 +55,7 @@ errno_t pthread_cond_broadcast(pthread_cond_t *cond) { // favor *NSYNC if this is a process private condition variable // if using Mike Burrows' code isn't possible, use a naive impl if (!cond->_footek) { - nsync_cv_broadcast((nsync_cv *)cond); + nsync_cv_broadcast((nsync_cv *)cond->_nsync); return 0; } #endif @@ -63,6 +63,6 @@ errno_t pthread_cond_broadcast(pthread_cond_t *cond) { // roll forward the monotonic sequence atomic_fetch_add_explicit(&cond->_sequence, 1, memory_order_acq_rel); if (atomic_load_explicit(&cond->_waiters, memory_order_acquire)) - nsync_futex_wake_((atomic_int *)&cond->_sequence, INT_MAX, cond->_pshared); + cosmo_futex_wake((atomic_int *)&cond->_sequence, INT_MAX, cond->_pshared); return 0; } diff --git a/libc/thread/pthread_cond_destroy.c b/libc/thread/pthread_cond_destroy.c index c5a180e4a..bb0783671 100644 --- a/libc/thread/pthread_cond_destroy.c +++ b/libc/thread/pthread_cond_destroy.c @@ -33,7 +33,7 @@ errno_t pthread_cond_destroy(pthread_cond_t *cond) { // check if there's active waiters #if PTHREAD_USE_NSYNC if (!cond->_pshared) { - if (((nsync_cv *)cond)->waiters) + if (((nsync_cv *)cond->_nsync)->waiters) return EINVAL; } else { if (atomic_load_explicit(&cond->_waiters, memory_order_relaxed)) diff --git a/libc/thread/pthread_cond_signal.c b/libc/thread/pthread_cond_signal.c index d3ac46844..fe6244d1e 100644 --- a/libc/thread/pthread_cond_signal.c +++ b/libc/thread/pthread_cond_signal.c @@ -16,11 +16,11 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/cosmo.h" #include "libc/dce.h" #include "libc/intrin/atomic.h" #include "libc/thread/thread.h" #include "third_party/nsync/cv.h" -#include "third_party/nsync/futex.internal.h" __static_yoink("nsync_mu_lock"); __static_yoink("nsync_mu_unlock"); @@ -43,7 +43,7 @@ __static_yoink("nsync_mu_trylock"); * @see pthread_cond_broadcast * @see pthread_cond_wait */ -errno_t pthread_cond_signal(pthread_cond_t *cond) { +errno_t _pthread_cond_signal(pthread_cond_t *cond) { #if PTHREAD_USE_NSYNC // do nothing if pthread_cond_timedwait() hasn't been called yet @@ -54,7 +54,7 @@ errno_t pthread_cond_signal(pthread_cond_t *cond) { // favor *NSYNC if this is a process private condition variable // if using Mike Burrows' code isn't possible, use a naive impl if (!cond->_footek) { - nsync_cv_signal((nsync_cv *)cond); + nsync_cv_signal((nsync_cv *)cond->_nsync); return 0; } #endif @@ -62,6 +62,8 @@ errno_t pthread_cond_signal(pthread_cond_t *cond) { // roll forward the monotonic sequence atomic_fetch_add_explicit(&cond->_sequence, 1, memory_order_acq_rel); if (atomic_load_explicit(&cond->_waiters, memory_order_acquire)) - nsync_futex_wake_((atomic_int *)&cond->_sequence, 1, cond->_pshared); + cosmo_futex_wake((atomic_int *)&cond->_sequence, 1, cond->_pshared); return 0; } + +__weak_reference(_pthread_cond_signal, pthread_cond_signal); diff --git a/libc/thread/pthread_cond_timedwait.c b/libc/thread/pthread_cond_timedwait.c index 8e2225cfc..9e4daff39 100644 --- a/libc/thread/pthread_cond_timedwait.c +++ b/libc/thread/pthread_cond_timedwait.c @@ -18,6 +18,8 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/calls/cp.internal.h" +#include "libc/calls/struct/timespec.h" +#include "libc/cosmo.h" #include "libc/dce.h" #include "libc/errno.h" #include "libc/intrin/atomic.h" @@ -28,7 +30,6 @@ #include "libc/thread/thread2.h" #include "third_party/nsync/common.internal.h" #include "third_party/nsync/cv.h" -#include "third_party/nsync/futex.internal.h" #include "third_party/nsync/time.h" __static_yoink("nsync_mu_lock"); @@ -42,13 +43,13 @@ struct PthreadWait { static bool can_use_nsync(uint64_t muword) { return !IsXnuSilicon() && // - MUTEX_TYPE(muword) == PTHREAD_MUTEX_NORMAL && + MUTEX_TYPE(muword) != PTHREAD_MUTEX_RECURSIVE && MUTEX_PSHARED(muword) == PTHREAD_PROCESS_PRIVATE; } static void pthread_cond_leave(void *arg) { struct PthreadWait *wait = (struct PthreadWait *)arg; - if (pthread_mutex_lock(wait->mutex)) + if (_pthread_mutex_lock(wait->mutex)) __builtin_trap(); atomic_fetch_sub_explicit(&wait->cond->_waiters, 1, memory_order_acq_rel); } @@ -67,15 +68,15 @@ static errno_t pthread_cond_timedwait_impl(pthread_cond_t *cond, // start waiting on condition variable atomic_fetch_add_explicit(&cond->_waiters, 1, memory_order_acq_rel); - if (pthread_mutex_unlock(mutex)) + if (_pthread_mutex_unlock(mutex)) __builtin_trap(); // wait for sequence change, timeout, or cancelation int rc; struct PthreadWait waiter = {cond, mutex}; pthread_cleanup_push(pthread_cond_leave, &waiter); - rc = nsync_futex_wait_((atomic_int *)&cond->_sequence, seq1, cond->_pshared, - cond->_clock, abstime); + rc = cosmo_futex_wait((atomic_int *)&cond->_sequence, seq1, cond->_pshared, + cond->_clock, abstime); pthread_cleanup_pop(true); if (rc == -EAGAIN) rc = 0; @@ -109,8 +110,8 @@ static errno_t pthread_cond_timedwait_impl(pthread_cond_t *cond, * @see pthread_cond_signal() * @cancelationpoint */ -errno_t pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, - const struct timespec *abstime) { +errno_t _pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, + const struct timespec *abstime) { // validate arguments struct PosixThread *pt; @@ -123,9 +124,9 @@ errno_t pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, uint64_t muword = atomic_load_explicit(&mutex->_word, memory_order_relaxed); // check that mutex is held by caller - if (MUTEX_TYPE(muword) == PTHREAD_MUTEX_ERRORCHECK && - MUTEX_OWNER(muword) != gettid()) - return EPERM; + if (IsModeDbg() || MUTEX_TYPE(muword) == PTHREAD_MUTEX_ERRORCHECK) + if (__deadlock_tracked(mutex) == 0) + return EPERM; // if the cond is process shared then the mutex needs to be too if ((cond->_pshared == PTHREAD_PROCESS_SHARED) ^ @@ -153,7 +154,7 @@ errno_t pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, // if using Mike Burrows' code isn't possible, use a naive impl if (!cond->_footek) { err = nsync_cv_wait_with_deadline( - (nsync_cv *)cond, (nsync_mu *)mutex, cond->_clock, + (nsync_cv *)cond->_nsync, (nsync_mu *)mutex->_nsync, cond->_clock, abstime ? *abstime : nsync_time_no_deadline, 0); } else { err = pthread_cond_timedwait_impl(cond, mutex, abstime); @@ -164,3 +165,5 @@ errno_t pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, END_CANCELATION_POINT; return err; } + +__weak_reference(_pthread_cond_timedwait, pthread_cond_timedwait); diff --git a/libc/thread/pthread_cond_wait.c b/libc/thread/pthread_cond_wait.c index df7d42dd1..e6ffd619c 100644 --- a/libc/thread/pthread_cond_wait.c +++ b/libc/thread/pthread_cond_wait.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" #include "libc/thread/thread2.h" @@ -39,6 +40,8 @@ * @see pthread_cond_signal * @cancelationpoint */ -errno_t pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) { +errno_t _pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) { return pthread_cond_timedwait(cond, mutex, 0); } + +__weak_reference(_pthread_cond_wait, pthread_cond_wait); diff --git a/libc/thread/pthread_create.c b/libc/thread/pthread_create.c index ec19ee9a7..974a3b592 100644 --- a/libc/thread/pthread_create.c +++ b/libc/thread/pthread_create.c @@ -18,10 +18,12 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" #include "libc/calls/calls.h" +#include "libc/calls/sig.internal.h" #include "libc/calls/struct/sigaltstack.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/struct/sigset.internal.h" #include "libc/calls/syscall-sysv.internal.h" +#include "libc/cosmo.h" #include "libc/dce.h" #include "libc/errno.h" #include "libc/fmt/itoa.h" @@ -29,6 +31,7 @@ #include "libc/intrin/describeflags.h" #include "libc/intrin/dll.h" #include "libc/intrin/kprintf.h" +#include "libc/intrin/stack.h" #include "libc/intrin/strace.h" #include "libc/intrin/weaken.h" #include "libc/log/internal.h" @@ -48,27 +51,23 @@ #include "libc/str/str.h" #include "libc/sysv/consts/auxv.h" #include "libc/sysv/consts/clone.h" -#include "libc/sysv/consts/map.h" #include "libc/sysv/consts/prot.h" #include "libc/sysv/consts/sig.h" #include "libc/sysv/consts/ss.h" #include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" #include "libc/thread/tls.h" +#include "third_party/nsync/wait_s.internal.h" __static_yoink("nsync_mu_lock"); __static_yoink("nsync_mu_unlock"); __static_yoink("nsync_mu_trylock"); __static_yoink("nsync_mu_rlock"); __static_yoink("nsync_mu_runlock"); -__static_yoink("_pthread_atfork"); __static_yoink("_pthread_onfork_prepare"); __static_yoink("_pthread_onfork_parent"); __static_yoink("_pthread_onfork_child"); -#define MAP_ANON_OPENBSD 0x1000 -#define MAP_STACK_OPENBSD 0x4000 - void _pthread_free(struct PosixThread *pt) { // thread must be removed from _pthread_list before calling @@ -80,12 +79,17 @@ void _pthread_free(struct PosixThread *pt) { // unmap stack if the cosmo runtime was responsible for mapping it if (pt->pt_flags & PT_OWNSTACK) - unassert(!munmap(pt->pt_attr.__stackaddr, pt->pt_attr.__stacksize)); + cosmo_stack_free(pt->pt_attr.__stackaddr, pt->pt_attr.__stacksize, + pt->pt_attr.__guardsize); + + // reclaim thread's cached nsync waiter object + if (pt->tib->tib_nsync) + nsync_waiter_destroy_(pt->tib->tib_nsync); // free any additional upstream system resources // our fork implementation wipes this handle in child automatically uint64_t syshand = - atomic_load_explicit(&pt->tib->tib_syshand, memory_order_acquire); + atomic_load_explicit(&pt->tib->tib_syshand, memory_order_relaxed); if (syshand) { if (IsWindows()) unassert(CloseHandle(syshand)); // non-inheritable @@ -94,13 +98,16 @@ void _pthread_free(struct PosixThread *pt) { } // free heap memory associated with thread - if (pt->pt_flags & PT_OWNSIGALTSTACK) - free(pt->pt_attr.__sigaltstackaddr); - free(pt->pt_tls); - free(pt); + bulk_free( + (void *[]){ + pt->pt_flags & PT_OWNSIGALTSTACK ? pt->pt_attr.__sigaltstackaddr : 0, + pt->pt_tls, + pt, + }, + 3); } -void _pthread_decimate(bool annihilation_only) { +void _pthread_decimate(enum PosixThreadStatus threshold) { struct PosixThread *pt; struct Dll *e, *e2, *list = 0; enum PosixThreadStatus status; @@ -115,26 +122,22 @@ void _pthread_decimate(bool annihilation_only) { pt = POSIXTHREAD_CONTAINER(e); if (atomic_load_explicit(&pt->pt_refs, memory_order_acquire) > 0) continue; // pthread_kill() has a lease on this thread + if (atomic_load_explicit(&pt->tib->tib_ctid, memory_order_acquire)) + continue; // thread is still using stack so leave alone status = atomic_load_explicit(&pt->pt_status, memory_order_acquire); - if (status != kPosixThreadZombie) - break; // zombies only exist at the end of the linked list - if (atomic_load_explicit(&pt->tib->tib_tid, memory_order_acquire)) - continue; // undead thread that should stop existing soon + if (status < threshold) { + if (threshold == kPosixThreadZombie) + break; // zombies only exist at the end of the linked list + continue; + } + if (status == kPosixThreadTerminated) + if (!(pt->pt_flags & PT_STATIC)) + STRACE("warning: you forgot to join or detach thread id %d", + atomic_load_explicit(&pt->tib->tib_ptid, memory_order_acquire)); dll_remove(&_pthread_list, e); dll_make_first(&list, e); } - // code like pthread_exit() needs to call this in order to know if - // it's appropriate to run exit() handlers however we really don't - // want to have a thread exiting block on a bunch of __maps locks! - // therefore we only take action if we'll destroy all but the self - if (annihilation_only) - if (!(_pthread_list == _pthread_list->prev && - _pthread_list == _pthread_list->next)) { - dll_make_last(&_pthread_list, list); - list = 0; - } - // release posix threads gil _pthread_unlock(); @@ -148,8 +151,7 @@ void _pthread_decimate(bool annihilation_only) { } } -static int PosixThread(void *arg, int tid) { - void *rc; +static int PosixThread(void *arg) { struct PosixThread *pt = arg; // setup scheduling @@ -160,27 +162,30 @@ static int PosixThread(void *arg, int tid) { // setup signal stack if (pt->pt_attr.__sigaltstacksize) { - struct sigaltstack ss; - ss.ss_sp = pt->pt_attr.__sigaltstackaddr; - ss.ss_size = pt->pt_attr.__sigaltstacksize; - ss.ss_flags = 0; - unassert(!sigaltstack(&ss, 0)); + struct sigaltstack *ss = alloca(sizeof(struct sigaltstack)); + ss->ss_sp = pt->pt_attr.__sigaltstackaddr; + ss->ss_size = pt->pt_attr.__sigaltstacksize; + ss->ss_flags = 0; + unassert(!sigaltstack(ss, 0)); } // set long jump handler so pthread_exit can bring control back here - if (!setjmp(pt->pt_exiter)) { - sigdelset(&pt->pt_attr.__sigmask, SIGTHR); + if (!__builtin_setjmp(pt->pt_exiter)) { + // setup signals for new thread + pt->pt_attr.__sigmask &= ~(1ull << (SIGTHR - 1)); if (IsWindows() || IsMetal()) { atomic_store_explicit(&__get_tls()->tib_sigmask, pt->pt_attr.__sigmask, memory_order_release); + if (_weaken(__sig_check)) + _weaken(__sig_check)(); } else { sys_sigprocmask(SIG_SETMASK, &pt->pt_attr.__sigmask, 0); } - rc = pt->pt_start(pt->pt_arg); + void *ret = pt->pt_start(pt->pt_val); // ensure pthread_cleanup_pop(), and pthread_exit() popped cleanup unassert(!pt->pt_cleanup); // calling pthread_exit() will either jump back here, or call exit - pthread_exit(rc); + pthread_exit(ret); } // avoid signal handler being triggered after we trash our own stack @@ -190,60 +195,24 @@ static int PosixThread(void *arg, int tid) { return 0; } -static bool TellOpenbsdThisIsStackMemory(void *addr, size_t size) { - return __sys_mmap( - addr, size, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_FIXED | MAP_ANON_OPENBSD | MAP_STACK_OPENBSD, -1, - 0, 0) == addr; -} - -// OpenBSD only permits RSP to occupy memory that's been explicitly -// defined as stack memory, i.e. `lo <= %rsp < hi` must be the case -static errno_t FixupCustomStackOnOpenbsd(pthread_attr_t *attr) { - - // get interval - uintptr_t lo = (uintptr_t)attr->__stackaddr; - uintptr_t hi = lo + attr->__stacksize; - - // squeeze interval - lo = (lo + __pagesize - 1) & -__pagesize; - hi = hi & -__pagesize; - - // tell os it's stack memory - errno_t olderr = errno; - if (!TellOpenbsdThisIsStackMemory((void *)lo, hi - lo)) { - errno_t err = errno; - errno = olderr; - return err; - } - - // update attributes with usable stack address - attr->__stackaddr = (void *)lo; - attr->__stacksize = hi - lo; - return 0; -} - static errno_t pthread_create_impl(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg, sigset_t oldsigs) { - int rc, e = errno; + errno_t err; struct PosixThread *pt; // create posix thread object - if (!(pt = calloc(1, sizeof(struct PosixThread)))) { - errno = e; + if (!(pt = calloc(1, sizeof(struct PosixThread)))) return EAGAIN; - } dll_init(&pt->list); pt->pt_locale = &__global_locale; pt->pt_start = start_routine; - pt->pt_arg = arg; + pt->pt_val = arg; // create thread local storage memory if (!(pt->pt_tls = _mktls(&pt->tib))) { free(pt); - errno = e; return EAGAIN; } @@ -260,44 +229,25 @@ static errno_t pthread_create_impl(pthread_t *thread, // caller supplied their own stack // assume they know what they're doing as much as possible if (IsOpenbsd()) { - if ((rc = FixupCustomStackOnOpenbsd(&pt->pt_attr))) { + if (!FixupCustomStackOnOpenbsd(&pt->pt_attr)) { _pthread_free(pt); - return rc; + return EPERM; } } } else { // cosmo is managing the stack - int pagesize = __pagesize; - pt->pt_attr.__guardsize = ROUNDUP(pt->pt_attr.__guardsize, pagesize); - pt->pt_attr.__stacksize = pt->pt_attr.__stacksize; - if (pt->pt_attr.__guardsize + pagesize > pt->pt_attr.__stacksize) { + pt->pt_flags |= PT_OWNSTACK; + errno_t err = + cosmo_stack_alloc(&pt->pt_attr.__stacksize, &pt->pt_attr.__guardsize, + &pt->pt_attr.__stackaddr); + if (err) { _pthread_free(pt); - return EINVAL; - } - pt->pt_attr.__stackaddr = - mmap(0, pt->pt_attr.__stacksize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (pt->pt_attr.__stackaddr != MAP_FAILED) { - if (IsOpenbsd()) - if (!TellOpenbsdThisIsStackMemory(pt->pt_attr.__stackaddr, - pt->pt_attr.__stacksize)) - notpossible; - if (pt->pt_attr.__guardsize) - if (mprotect(pt->pt_attr.__stackaddr, pt->pt_attr.__guardsize, - PROT_NONE | PROT_GUARD)) - notpossible; - } - if (!pt->pt_attr.__stackaddr || pt->pt_attr.__stackaddr == MAP_FAILED) { - rc = errno; - _pthread_free(pt); - errno = e; - if (rc == EINVAL || rc == EOVERFLOW) { + if (err == EINVAL || err == EOVERFLOW) { return EINVAL; } else { return EAGAIN; } } - pt->pt_flags |= PT_OWNSTACK; } // setup signal stack @@ -306,7 +256,7 @@ static errno_t pthread_create_impl(pthread_t *thread, if (!(pt->pt_attr.__sigaltstackaddr = malloc(pt->pt_attr.__sigaltstacksize))) { _pthread_free(pt); - return errno; + return EAGAIN; } pt->pt_flags |= PT_OWNSIGALTSTACK; } @@ -329,31 +279,41 @@ static errno_t pthread_create_impl(pthread_t *thread, memory_order_relaxed); break; default: - _pthread_free(pt); - return EINVAL; + // pthread_attr_setdetachstate() makes this impossible + __builtin_unreachable(); } + // if pthread_attr_setdetachstate() was used then it's possible for + // the `pt` object to be freed before this clone call has returned! + atomic_store_explicit(&pt->pt_refs, 1, memory_order_relaxed); + // add thread to global list // we add it to the beginning since zombies go at the end _pthread_lock(); dll_make_first(&_pthread_list, &pt->list); _pthread_unlock(); + // we don't normally do this, but it's important to write the result + // memory before spawning the thread, so it's visible to the threads + *thread = (pthread_t)pt; + // launch PosixThread(pt) in new thread - if ((rc = clone(PosixThread, pt->pt_attr.__stackaddr, pt->pt_attr.__stacksize, - CLONE_VM | CLONE_THREAD | CLONE_FS | CLONE_FILES | - CLONE_SIGHAND | CLONE_SYSVSEM | CLONE_SETTLS | - CLONE_PARENT_SETTID | CLONE_CHILD_SETTID | - CLONE_CHILD_CLEARTID, - pt, &pt->ptid, __adj_tls(pt->tib), &pt->tib->tib_tid))) { + if ((err = clone( + PosixThread, pt->pt_attr.__stackaddr, pt->pt_attr.__stacksize, + CLONE_VM | CLONE_THREAD | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | + CLONE_SYSVSEM | CLONE_SETTLS | CLONE_PARENT_SETTID | + CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID, + pt, &pt->tib->tib_ptid, __adj_tls(pt->tib), &pt->tib->tib_ctid))) { + *thread = 0; // posix doesn't require we do this _pthread_lock(); dll_remove(&_pthread_list, &pt->list); _pthread_unlock(); _pthread_free(pt); - return rc; + if (err == ENOMEM) + err = EAGAIN; + return err; } - *thread = (pthread_t)pt; return 0; } @@ -401,8 +361,8 @@ static const char *DescribeHandle(char buf[12], errno_t err, pthread_t *th) { * │ _lwp_create │ * └──────────────┘ * - * @param thread if non-null is used to output the thread id - * upon successful completion + * @param thread is used to output the thread id upon success, which + * must be non-null; upon failure, its value is undefined * @param attr points to launch configuration, or may be null * to use sensible defaults; it must be initialized using * pthread_attr_init() @@ -418,12 +378,18 @@ static const char *DescribeHandle(char buf[12], errno_t err, pthread_t *th) { errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg) { errno_t err; - _pthread_decimate(false); + errno_t olderr = errno; + _pthread_decimate(kPosixThreadZombie); BLOCK_SIGNALS; err = pthread_create_impl(thread, attr, start_routine, arg, _SigMask); ALLOW_SIGNALS; STRACE("pthread_create([%s], %p, %t, %p) → %s", DescribeHandle(alloca(12), err, thread), attr, start_routine, arg, DescribeErrno(err)); + if (!err) { + _pthread_unref(*(struct PosixThread **)thread); + } else { + errno = olderr; + } return err; } diff --git a/libc/thread/pthread_decimate_np.c b/libc/thread/pthread_decimate_np.c index 3027dc7fa..8299db3a2 100644 --- a/libc/thread/pthread_decimate_np.c +++ b/libc/thread/pthread_decimate_np.c @@ -16,22 +16,32 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/cosmo.h" +#include "libc/intrin/stack.h" #include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" /** * Garbage collects POSIX threads runtime. * - * Let's say you want to run a memory leak detector. You can say: + * This function frees unreferenced zombie threads and empties cache + * memory associated with the Cosmopolitan POSIX threads runtime. + * + * Here's an example use case for this function. Let's say you want to + * create a malloc() memory leak detector. If your program was running + * threads earlier, then there might still be allocations lingering + * around, that'll give you false positives. To fix this, what you would + * do is call the following, right before running your leak detector: * * while (!pthread_orphan_np()) * pthread_decimate_np(); * - * To wait until all threads have exited. + * Which will wait until all threads have exited and their memory freed. * * @return 0 on success, or errno on error */ int pthread_decimate_np(void) { - _pthread_decimate(false); + _pthread_decimate(kPosixThreadZombie); + cosmo_stack_clear(); return 0; } diff --git a/libc/thread/pthread_exit.c b/libc/thread/pthread_exit.c index 0d625d4d7..6f8199203 100644 --- a/libc/thread/pthread_exit.c +++ b/libc/thread/pthread_exit.c @@ -18,10 +18,13 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" #include "libc/atomic.h" +#include "libc/calls/calls.h" +#include "libc/cosmo.h" #include "libc/cxxabi.h" #include "libc/dce.h" #include "libc/intrin/atomic.h" #include "libc/intrin/cxaatexit.h" +#include "libc/intrin/describebacktrace.h" #include "libc/intrin/strace.h" #include "libc/intrin/weaken.h" #include "libc/limits.h" @@ -33,7 +36,6 @@ #include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" #include "libc/thread/tls.h" -#include "third_party/nsync/futex.internal.h" #include "third_party/nsync/wait_s.internal.h" /** @@ -69,7 +71,7 @@ * @noreturn */ wontreturn void pthread_exit(void *rc) { - int orphan; + unsigned population; struct CosmoTib *tib; struct PosixThread *pt; enum PosixThreadStatus status, transition; @@ -88,16 +90,29 @@ wontreturn void pthread_exit(void *rc) { // set state pt->pt_flags |= PT_NOCANCEL | PT_EXITING; - pt->pt_rc = rc; + pt->pt_val = rc; // free resources __cxa_thread_finalize(); // run atexit handlers if orphaned thread - _pthread_decimate(true); - if ((orphan = pthread_orphan_np())) - if (_weaken(__cxa_finalize)) - _weaken(__cxa_finalize)(NULL); + // notice how we avoid acquiring the pthread gil + if (!(population = atomic_fetch_sub(&_pthread_count, 1) - 1)) { + // we know for certain we're an orphan. any other threads that + // exist, will terminate and clear their tid very soon. but some + // goofball could spawn more threads from atexit() handlers. we'd + // also like to avoid looping forever here, by auto-joining threads + // that leaked, because the user forgot to join them or detach them + for (;;) { + if (_weaken(__cxa_finalize)) + _weaken(__cxa_finalize)(NULL); + _pthread_decimate(kPosixThreadTerminated); + if (pthread_orphan_np()) { + population = atomic_load(&_pthread_count); + break; + } + } + } // transition the thread to a terminated state status = atomic_load_explicit(&pt->pt_status, memory_order_acquire); @@ -127,7 +142,7 @@ wontreturn void pthread_exit(void *rc) { // thread has been terminated. The behavior shall be as if the // implementation called exit() with a zero argument at thread // termination time." ──Quoth POSIX.1-2017 - if (orphan) { + if (!population) { for (int i = __fini_array_end - __fini_array_start; i--;) ((void (*)(void))__fini_array_start[i])(); _Exit(0); @@ -136,14 +151,14 @@ wontreturn void pthread_exit(void *rc) { // check if the main thread has died whilst children live // note that the main thread is joinable by child threads if (pt->pt_flags & PT_STATIC) { - atomic_store_explicit(&tib->tib_tid, 0, memory_order_release); - nsync_futex_wake_((atomic_int *)&tib->tib_tid, INT_MAX, - !IsWindows() && !IsXnu()); + atomic_store_explicit(&tib->tib_ctid, 0, memory_order_release); + cosmo_futex_wake((atomic_int *)&tib->tib_ctid, INT_MAX, + !IsWindows() && !IsXnu()); _Exit1(0); } // this is a child thread - longjmp(pt->pt_exiter, 1); + __builtin_longjmp(pt->pt_exiter, 1); } __weak_reference(pthread_exit, thr_exit); diff --git a/libc/thread/pthread_getattr_np.c b/libc/thread/pthread_getattr_np.c index 7742b329f..a57472149 100644 --- a/libc/thread/pthread_getattr_np.c +++ b/libc/thread/pthread_getattr_np.c @@ -16,18 +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/struct/rlimit.h" -#include "libc/dce.h" -#include "libc/intrin/atomic.h" -#include "libc/intrin/maps.h" -#include "libc/limits.h" -#include "libc/macros.h" -#include "libc/runtime/runtime.h" #include "libc/str/str.h" -#include "libc/sysv/consts/auxv.h" -#include "libc/sysv/consts/rlim.h" -#include "libc/sysv/consts/rlimit.h" #include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" @@ -72,10 +61,5 @@ errno_t pthread_getattr_np(pthread_t thread, pthread_attr_t *attr) { default: __builtin_unreachable(); } - if (!attr->__stacksize && (pt->pt_flags & PT_STATIC)) { - attr->__stackaddr = __maps.stack.addr; - attr->__stacksize = __maps.stack.size; - attr->__guardsize = 0; - } return 0; } diff --git a/libc/thread/pthread_kill.c b/libc/thread/pthread_kill.c index 472da205e..6c1722965 100644 --- a/libc/thread/pthread_kill.c +++ b/libc/thread/pthread_kill.c @@ -24,6 +24,7 @@ #include "libc/intrin/atomic.h" #include "libc/intrin/describeflags.h" #include "libc/intrin/strace.h" +#include "libc/runtime/internal.h" #include "libc/runtime/syslib.internal.h" #include "libc/sysv/consts/sicode.h" #include "libc/thread/posixthread.internal.h" @@ -43,7 +44,15 @@ errno_t pthread_kill(pthread_t thread, int sig) { int err = 0; struct PosixThread *pt; pt = (struct PosixThread *)thread; - if (!(1 <= sig && sig <= 64)) { + if (pt) + _pthread_ref(pt); + if (!thread) { + // avoid crashing on easily predictable npe + // chances are you need a barrier to synchronize startup + err = EFAULT; + } else if (!(1 <= sig && sig <= 64)) { + // cosmo only supports this many signals + // some platforms have more but we're not sure what they do err = EINVAL; } else if (thread == __get_tls()->tib_pthread) { err = raise(sig); // XNU will EDEADLK it otherwise @@ -56,17 +65,25 @@ errno_t pthread_kill(pthread_t thread, int sig) { if (IsXnuSilicon()) { err = __syslib->__pthread_kill(_pthread_syshand(pt), sig); } else { + int r = 0; int e = errno; - if (sys_tkill(_pthread_tid(pt), sig, pt->tib)) { + int tid = _pthread_tid(pt); + if (IsLinux() || IsFreebsd()) { + r = sys_tgkill(__pid, tid, sig); + } else { + r = sys_tkill(tid, sig, pt->tib); + } + if (r) { err = errno; errno = e; } } - if (err == ESRCH) { + if (err == ESRCH) err = 0; // we already reported this - } } STRACE("pthread_kill(%d, %G) → %s", _pthread_tid(pt), sig, DescribeErrno(err)); + if (pt) + _pthread_unref(pt); return err; } diff --git a/libc/calls/getppid-nt.c b/libc/thread/pthread_mutex_consistent.c similarity index 69% rename from libc/calls/getppid-nt.c rename to libc/thread/pthread_mutex_consistent.c index 438cafc61..44a5fd5f6 100644 --- a/libc/calls/getppid-nt.c +++ b/libc/thread/pthread_mutex_consistent.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ Copyright 2024 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,22 +16,26 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/syscall-nt.internal.h" -#include "libc/nt/enum/status.h" -#include "libc/nt/nt/process.h" -#include "libc/nt/process.h" -#include "libc/nt/runtime.h" -#include "libc/nt/struct/processbasicinformation.h" +#include "libc/cosmo.h" +#include "libc/dce.h" +#include "libc/intrin/atomic.h" +#include "libc/thread/lock.h" +#include "libc/thread/thread.h" -textwindows int sys_getppid_nt(void) { - struct NtProcessBasicInformation ProcessInformation; - uint32_t gotsize = 0; - if (!NtError( - NtQueryInformationProcess(GetCurrentProcess(), 0, &ProcessInformation, - sizeof(ProcessInformation), &gotsize)) && - gotsize >= sizeof(ProcessInformation) && - ProcessInformation.InheritedFromUniqueProcessId) { - return ProcessInformation.InheritedFromUniqueProcessId; - } - return GetCurrentProcessId(); +/** + * Recovers mutex whose owner died. + * + * @return 0 on success, or errno on error + */ +int pthread_mutex_consistent(pthread_mutex_t *mutex) { + + // The POSIX concept of robust mutexes is a bit cray. So let's change + // things up a bit. Rather than implementing all those goofy behaviors + // we shall simply use this function to weasel around the ownership + // check in pthread_mutex_unlock(). + uint64_t word = atomic_load_explicit(&mutex->_word, memory_order_relaxed); + if (IsModeDbg() || MUTEX_TYPE(word) == PTHREAD_MUTEX_ERRORCHECK) + __deadlock_track(mutex, 0); + + return 0; } diff --git a/libc/intrin/pthread_mutex_destroy.c b/libc/thread/pthread_mutex_destroy.c similarity index 100% rename from libc/intrin/pthread_mutex_destroy.c rename to libc/thread/pthread_mutex_destroy.c diff --git a/libc/intrin/pthread_mutex_init.c b/libc/thread/pthread_mutex_init.c similarity index 97% rename from libc/intrin/pthread_mutex_init.c rename to libc/thread/pthread_mutex_init.c index 8801f2372..1ce34716b 100644 --- a/libc/intrin/pthread_mutex_init.c +++ b/libc/thread/pthread_mutex_init.c @@ -24,7 +24,7 @@ * pthread_mutex_t lock; * pthread_mutexattr_t attr; * pthread_mutexattr_init(&attr); - * pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); + * pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); * pthread_mutex_init(&lock, &attr); * pthread_mutexattr_destroy(&attr); * // ... diff --git a/libc/intrin/pthread_mutexattr_destroy.c b/libc/thread/pthread_mutexattr_destroy.c similarity index 100% rename from libc/intrin/pthread_mutexattr_destroy.c rename to libc/thread/pthread_mutexattr_destroy.c diff --git a/libc/intrin/pthread_mutexattr_getpshared.c b/libc/thread/pthread_mutexattr_getpshared.c similarity index 100% rename from libc/intrin/pthread_mutexattr_getpshared.c rename to libc/thread/pthread_mutexattr_getpshared.c diff --git a/libc/intrin/pthread_mutexattr_gettype.c b/libc/thread/pthread_mutexattr_gettype.c similarity index 98% rename from libc/intrin/pthread_mutexattr_gettype.c rename to libc/thread/pthread_mutexattr_gettype.c index 9b85dca0d..6e4caa149 100644 --- a/libc/intrin/pthread_mutexattr_gettype.c +++ b/libc/thread/pthread_mutexattr_gettype.c @@ -23,6 +23,7 @@ * Gets mutex type. * * @param type will be set to one of these on success + * - `PTHREAD_MUTEX_DEFAULT` * - `PTHREAD_MUTEX_NORMAL` * - `PTHREAD_MUTEX_RECURSIVE` * - `PTHREAD_MUTEX_ERRORCHECK` diff --git a/libc/intrin/pthread_mutexattr_init.c b/libc/thread/pthread_mutexattr_init.c similarity index 100% rename from libc/intrin/pthread_mutexattr_init.c rename to libc/thread/pthread_mutexattr_init.c diff --git a/libc/intrin/pthread_mutexattr_setpshared.c b/libc/thread/pthread_mutexattr_setpshared.c similarity index 100% rename from libc/intrin/pthread_mutexattr_setpshared.c rename to libc/thread/pthread_mutexattr_setpshared.c diff --git a/libc/intrin/pthread_mutexattr_settype.c b/libc/thread/pthread_mutexattr_settype.c similarity index 97% rename from libc/intrin/pthread_mutexattr_settype.c rename to libc/thread/pthread_mutexattr_settype.c index 70a421abe..aefe262f4 100644 --- a/libc/intrin/pthread_mutexattr_settype.c +++ b/libc/thread/pthread_mutexattr_settype.c @@ -24,6 +24,7 @@ * Sets mutex type. * * @param type can be one of + * - `PTHREAD_MUTEX_DEFAULT` * - `PTHREAD_MUTEX_NORMAL` * - `PTHREAD_MUTEX_RECURSIVE` * - `PTHREAD_MUTEX_ERRORCHECK` @@ -32,6 +33,7 @@ */ errno_t pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type) { switch (type) { + case PTHREAD_MUTEX_DEFAULT: case PTHREAD_MUTEX_NORMAL: case PTHREAD_MUTEX_RECURSIVE: case PTHREAD_MUTEX_ERRORCHECK: diff --git a/libc/thread/pthread_rwlock_destroy.c b/libc/thread/pthread_rwlock_destroy.c index 39942c2d0..a3b693d6f 100644 --- a/libc/thread/pthread_rwlock_destroy.c +++ b/libc/thread/pthread_rwlock_destroy.c @@ -16,16 +16,30 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/errno.h" +#include "libc/intrin/atomic.h" #include "libc/str/str.h" #include "libc/thread/thread.h" +#include "third_party/nsync/mu.h" /** * Destroys read-write lock. * * @return 0 on success, or error number on failure - * @raise EINVAL if any threads still hold the lock + * @raise EBUSY if any threads still hold the lock */ errno_t pthread_rwlock_destroy(pthread_rwlock_t *rwlock) { + + // check if lock is held + if (!rwlock->_pshared) { + nsync_mu *mu = (nsync_mu *)rwlock->_nsync; + if (atomic_load_explicit(&mu->word, memory_order_relaxed)) + return EBUSY; + } else { + if (atomic_load_explicit(&rwlock->_word, memory_order_relaxed)) + return EBUSY; + } + memset(rwlock, -1, sizeof(*rwlock)); return 0; } diff --git a/libc/thread/pthread_rwlock_init.c b/libc/thread/pthread_rwlock_init.c index 54fe08ece..dea3f67a9 100644 --- a/libc/thread/pthread_rwlock_init.c +++ b/libc/thread/pthread_rwlock_init.c @@ -26,6 +26,8 @@ */ errno_t pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr) { - *rwlock = (pthread_rwlock_t){0}; + *rwlock = (pthread_rwlock_t){ + ._pshared = attr ? *attr : PTHREAD_PROCESS_PRIVATE, + }; return 0; } diff --git a/libc/thread/pthread_rwlock_rdlock.c b/libc/thread/pthread_rwlock_rdlock.c index 781c0b6c9..e097bb0ef 100644 --- a/libc/thread/pthread_rwlock_rdlock.c +++ b/libc/thread/pthread_rwlock_rdlock.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/atomic.h" #include "libc/thread/thread.h" #include "third_party/nsync/mu.h" @@ -24,7 +25,28 @@ * * @return 0 on success, or errno on error */ -errno_t pthread_rwlock_rdlock(pthread_rwlock_t *rwlock) { - nsync_mu_rlock((nsync_mu *)rwlock); - return 0; +errno_t pthread_rwlock_rdlock(pthread_rwlock_t *lk) { + +#if PTHREAD_USE_NSYNC + // use nsync if possible + if (!lk->_pshared) { + nsync_mu_rlock((nsync_mu *)lk->_nsync); + return 0; + } +#endif + + // naive implementation + uint32_t w = 0; + for (;;) { + if (w & 1) + for (;;) + if (~(w = atomic_load_explicit(&lk->_word, memory_order_relaxed)) & 1) + break; + // xxx: avoid writer starvation in pthread_rwlock_rdlock_test + while (atomic_load(&lk->_waiters)) + pthread_yield_np(); + if (atomic_compare_exchange_weak_explicit( + &lk->_word, &w, w + 2, memory_order_acquire, memory_order_relaxed)) + return 0; + } } diff --git a/libc/thread/pthread_rwlock_tryrdlock.c b/libc/thread/pthread_rwlock_tryrdlock.c index 35d51a051..1969c3a41 100644 --- a/libc/thread/pthread_rwlock_tryrdlock.c +++ b/libc/thread/pthread_rwlock_tryrdlock.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/errno.h" +#include "libc/intrin/atomic.h" #include "libc/thread/thread.h" #include "third_party/nsync/mu.h" @@ -29,9 +30,26 @@ * @raise EINVAL if `rwlock` doesn't refer to an initialized r/w lock */ errno_t pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock) { - if (nsync_mu_rtrylock((nsync_mu *)rwlock)) { - return 0; - } else { - return EBUSY; + +#if PTHREAD_USE_NSYNC + // use nsync if possible + if (!rwlock->_pshared) { + if (nsync_mu_rtrylock((nsync_mu *)rwlock->_nsync)) { + return 0; + } else { + return EBUSY; + } + } +#endif + + // naive implementation + uint32_t word = 0; + for (;;) { + if (word & 1) + return EBUSY; + if (atomic_compare_exchange_weak_explicit(&rwlock->_word, &word, word + 2, + memory_order_acquire, + memory_order_relaxed)) + return 0; } } diff --git a/libc/thread/pthread_rwlock_trywrlock.c b/libc/thread/pthread_rwlock_trywrlock.c index c685a39dc..49b39e38f 100644 --- a/libc/thread/pthread_rwlock_trywrlock.c +++ b/libc/thread/pthread_rwlock_trywrlock.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/errno.h" +#include "libc/intrin/atomic.h" #include "libc/thread/thread.h" #include "third_party/nsync/mu.h" @@ -28,10 +29,23 @@ * @raise EINVAL if `rwlock` doesn't refer to an initialized r/w lock */ errno_t pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock) { - if (nsync_mu_trylock((nsync_mu *)rwlock)) { - rwlock->_iswrite = 1; - return 0; - } else { - return EBUSY; + +#if PTHREAD_USE_NSYNC + // use nsync if possible + if (!rwlock->_pshared) { + if (nsync_mu_trylock((nsync_mu *)rwlock->_nsync)) { + rwlock->_iswrite = 1; + return 0; + } else { + return EBUSY; + } } +#endif + + // naive implementation + uint32_t word = 0; + if (atomic_compare_exchange_strong_explicit( + &rwlock->_word, &word, 1, memory_order_acquire, memory_order_relaxed)) + return 0; + return EBUSY; } diff --git a/libc/thread/pthread_rwlock_unlock.c b/libc/thread/pthread_rwlock_unlock.c index 1918491c8..5b5feaa02 100644 --- a/libc/thread/pthread_rwlock_unlock.c +++ b/libc/thread/pthread_rwlock_unlock.c @@ -16,6 +16,8 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/errno.h" +#include "libc/intrin/atomic.h" #include "libc/thread/thread.h" #include "third_party/nsync/mu.h" @@ -26,11 +28,33 @@ * @raise EINVAL if lock is in a bad state */ errno_t pthread_rwlock_unlock(pthread_rwlock_t *rwlock) { - if (rwlock->_iswrite) { - rwlock->_iswrite = 0; - nsync_mu_unlock((nsync_mu *)rwlock); - } else { - nsync_mu_runlock((nsync_mu *)rwlock); + +#if PTHREAD_USE_NSYNC + // use nsync if possible + if (!rwlock->_pshared) { + if (rwlock->_iswrite) { + rwlock->_iswrite = 0; + nsync_mu_unlock((nsync_mu *)rwlock->_nsync); + } else { + nsync_mu_runlock((nsync_mu *)rwlock->_nsync); + } + return 0; + } +#endif + + // naive implementation + uint32_t word = atomic_load_explicit(&rwlock->_word, memory_order_relaxed); + for (;;) { + if (word & 1) { + atomic_store_explicit(&rwlock->_word, 0, memory_order_release); + return 0; + } else if (word) { + if (atomic_compare_exchange_weak_explicit(&rwlock->_word, &word, word - 2, + memory_order_release, + memory_order_relaxed)) + return 0; + } else { + return EPERM; + } } - return 0; } diff --git a/libc/thread/pthread_rwlock_wrlock.c b/libc/thread/pthread_rwlock_wrlock.c index 3eea88db7..382eba828 100644 --- a/libc/thread/pthread_rwlock_wrlock.c +++ b/libc/thread/pthread_rwlock_wrlock.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/atomic.h" #include "libc/thread/thread.h" #include "third_party/nsync/mu.h" @@ -25,7 +26,26 @@ * @return 0 on success, or errno on error */ errno_t pthread_rwlock_wrlock(pthread_rwlock_t *rwlock) { - nsync_mu_lock((nsync_mu *)rwlock); - rwlock->_iswrite = 1; - return 0; + +#if PTHREAD_USE_NSYNC + // use nsync if possible + if (!rwlock->_pshared) { + nsync_mu_lock((nsync_mu *)rwlock->_nsync); + rwlock->_iswrite = 1; + return 0; + } +#endif + + // naive implementation + uint32_t w = 0; + for (;;) { + if (atomic_compare_exchange_weak_explicit( + &rwlock->_word, &w, 1, memory_order_acquire, memory_order_relaxed)) + return 0; + atomic_fetch_add(&rwlock->_waiters, 1); + for (;;) + if (!(w = atomic_load_explicit(&rwlock->_word, memory_order_relaxed))) + break; + atomic_fetch_sub(&rwlock->_waiters, 1); + } } diff --git a/libc/thread/pthread_rwlockattr_getpshared.c b/libc/thread/pthread_rwlockattr_getpshared.c index 05507dcd5..5ebfb765b 100644 --- a/libc/thread/pthread_rwlockattr_getpshared.c +++ b/libc/thread/pthread_rwlockattr_getpshared.c @@ -23,7 +23,7 @@ * * @param pshared is set to one of the following * - `PTHREAD_PROCESS_PRIVATE` (default) - * - `PTHREAD_PROCESS_SHARED` (unsupported) + * - `PTHREAD_PROCESS_SHARED` * @return 0 on success, or error on failure */ errno_t pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, diff --git a/libc/thread/pthread_rwlockattr_setpshared.c b/libc/thread/pthread_rwlockattr_setpshared.c index d7378d6e8..49bf21efe 100644 --- a/libc/thread/pthread_rwlockattr_setpshared.c +++ b/libc/thread/pthread_rwlockattr_setpshared.c @@ -24,13 +24,14 @@ * * @param pshared can be one of * - `PTHREAD_PROCESS_PRIVATE` (default) - * - `PTHREAD_PROCESS_SHARED` (unsupported) + * - `PTHREAD_PROCESS_SHARED` * @return 0 on success, or error on failure * @raises EINVAL if `pshared` is invalid */ errno_t pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared) { switch (pshared) { case PTHREAD_PROCESS_PRIVATE: + case PTHREAD_PROCESS_SHARED: *attr = pshared; return 0; default: diff --git a/libc/thread/pthread_setcanceltype.c b/libc/thread/pthread_setcanceltype.c index f65187104..6aad36c6e 100644 --- a/libc/thread/pthread_setcanceltype.c +++ b/libc/thread/pthread_setcanceltype.c @@ -76,8 +76,10 @@ errno_t pthread_setcanceltype(int type, int *oldtype) { err = EINVAL; break; } +#ifdef MODE_DBG STRACE("pthread_setcanceltype(%s, [%s]) → %s", DescribeCancelType(alloca(12), 0, &type), DescribeCancelType(alloca(12), err, oldtype), DescribeErrno(err)); +#endif return err; } diff --git a/libc/intrin/pthread_spin_destroy.c b/libc/thread/pthread_spin_destroy.c similarity index 100% rename from libc/intrin/pthread_spin_destroy.c rename to libc/thread/pthread_spin_destroy.c diff --git a/libc/intrin/pthread_spin_init.c b/libc/thread/pthread_spin_init.c similarity index 100% rename from libc/intrin/pthread_spin_init.c rename to libc/thread/pthread_spin_init.c diff --git a/libc/intrin/pthread_spin_lock.c b/libc/thread/pthread_spin_lock.c similarity index 100% rename from libc/intrin/pthread_spin_lock.c rename to libc/thread/pthread_spin_lock.c diff --git a/libc/intrin/pthread_spin_trylock.c b/libc/thread/pthread_spin_trylock.c similarity index 100% rename from libc/intrin/pthread_spin_trylock.c rename to libc/thread/pthread_spin_trylock.c diff --git a/libc/intrin/pthread_spin_unlock.c b/libc/thread/pthread_spin_unlock.c similarity index 100% rename from libc/intrin/pthread_spin_unlock.c rename to libc/thread/pthread_spin_unlock.c diff --git a/libc/thread/pthread_timedjoin_np.c b/libc/thread/pthread_timedjoin_np.c index 9199f2e53..cd1643b8d 100644 --- a/libc/thread/pthread_timedjoin_np.c +++ b/libc/thread/pthread_timedjoin_np.c @@ -20,6 +20,8 @@ #include "libc/calls/cp.internal.h" #include "libc/calls/struct/timespec.h" #include "libc/calls/struct/timespec.internal.h" +#include "libc/cosmo.h" +#include "libc/dce.h" #include "libc/errno.h" #include "libc/fmt/itoa.h" #include "libc/intrin/atomic.h" @@ -30,7 +32,6 @@ #include "libc/thread/posixthread.internal.h" #include "libc/thread/thread2.h" #include "libc/thread/tls.h" -#include "third_party/nsync/futex.internal.h" static const char *DescribeReturnValue(char buf[30], int err, void **value) { char *p = buf; @@ -61,33 +62,34 @@ static const char *DescribeReturnValue(char buf[30], int err, void **value) { * @cancelationpoint */ static errno_t _pthread_wait(atomic_int *ctid, struct timespec *abstime) { - int x, e; - errno_t err = 0; - if (ctid == &__get_tls()->tib_tid) { - // "If an implementation detects that the value specified by the - // thread argument to pthread_join() refers to the calling thread, - // it is recommended that the function should fail and report an - // [EDEADLK] error." ──Quoth POSIX.1-2017 - err = EDEADLK; - } else { - // "If the thread calling pthread_join() is canceled, then the target - // thread shall not be detached." ──Quoth POSIX.1-2017 - if (!(err = pthread_testcancel_np())) { - BEGIN_CANCELATION_POINT; - while ((x = atomic_load_explicit(ctid, memory_order_acquire))) { - e = nsync_futex_wait_(ctid, x, !IsWindows() && !IsXnu(), CLOCK_REALTIME, - abstime); - if (e == -ECANCELED) { - err = ECANCELED; - break; - } else if (e == -ETIMEDOUT) { - err = EBUSY; - break; - } - } - END_CANCELATION_POINT; + + // "If an implementation detects that the value specified by the + // thread argument to pthread_join() refers to the calling thread, + // it is recommended that the function should fail and report an + // [EDEADLK] error." ──Quoth POSIX.1-2017 + if (ctid == &__get_tls()->tib_ctid) + return EDEADLK; + + // "If the thread calling pthread_join() is canceled, then the target + // thread shall not be detached." ──Quoth POSIX.1-2017 + errno_t err; + if ((err = pthread_testcancel_np())) + return err; + + BEGIN_CANCELATION_POINT; + int x; + while ((x = atomic_load_explicit(ctid, memory_order_acquire))) { + int e = cosmo_futex_wait(ctid, x, !IsWindows() && !IsXnu(), CLOCK_REALTIME, + abstime); + if (e == -ECANCELED) { + err = ECANCELED; + break; + } else if (e == -ETIMEDOUT) { + err = EBUSY; + break; } } + END_CANCELATION_POINT; return err; } @@ -116,12 +118,11 @@ static errno_t _pthread_wait(atomic_int *ctid, struct timespec *abstime) { errno_t pthread_timedjoin_np(pthread_t thread, void **value_ptr, struct timespec *abstime) { int tid; - errno_t err = 0; + errno_t err; struct PosixThread *pt; enum PosixThreadStatus status; pt = (struct PosixThread *)thread; unassert(thread); - _pthread_ref(pt); // "The behavior is undefined if the value specified by the thread // argument to pthread_join() does not refer to a joinable thread." @@ -133,15 +134,24 @@ errno_t pthread_timedjoin_np(pthread_t thread, void **value_ptr, // "The results of multiple simultaneous calls to pthread_join() // specifying the same target thread are undefined." // ──Quoth POSIX.1-2017 - if (!(err = _pthread_wait(&pt->tib->tib_tid, abstime))) { - atomic_store_explicit(&pt->pt_status, kPosixThreadZombie, - memory_order_release); - _pthread_zombify(pt); + if (!(err = _pthread_wait(&pt->tib->tib_ctid, abstime))) { if (value_ptr) - *value_ptr = pt->pt_rc; + *value_ptr = pt->pt_val; + if (atomic_load_explicit(&pt->pt_refs, memory_order_acquire)) { + _pthread_lock(); + dll_remove(&_pthread_list, &pt->list); + dll_make_last(&_pthread_list, &pt->list); + atomic_store_explicit(&pt->pt_status, kPosixThreadZombie, + memory_order_release); + _pthread_unlock(); + } else { + _pthread_lock(); + dll_remove(&_pthread_list, &pt->list); + _pthread_unlock(); + _pthread_free(pt); + } } - _pthread_unref(pt); STRACE("pthread_timedjoin_np(%d, %s, %s) → %s", tid, DescribeReturnValue(alloca(30), err, value_ptr), DescribeTimespec(err ? -1 : 0, abstime), DescribeErrno(err)); diff --git a/libc/thread/pthread_tryjoin_np.c b/libc/thread/pthread_tryjoin_np.c index 248b26928..39929646e 100644 --- a/libc/thread/pthread_tryjoin_np.c +++ b/libc/thread/pthread_tryjoin_np.c @@ -32,6 +32,7 @@ * if the thread called pthread_exit(), or `PTHREAD_CANCELED` if * pthread_cancel() destroyed the thread instead * @return 0 on success, or errno on error + * @raise EBUSY if thread has not yet terminated * @raise ECANCELED if calling thread was cancelled in masked mode * @cancelationpoint * @returnserrno diff --git a/libc/thread/sem_open.c b/libc/thread/sem_open.c index d708ef7e4..156bbc868 100644 --- a/libc/thread/sem_open.c +++ b/libc/thread/sem_open.c @@ -17,10 +17,12 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/atomic.h" #include "libc/calls/blockcancel.internal.h" #include "libc/calls/calls.h" #include "libc/calls/struct/stat.h" #include "libc/calls/syscall-sysv.internal.h" +#include "libc/cosmo.h" #include "libc/dce.h" #include "libc/errno.h" #include "libc/intrin/atomic.h" @@ -35,12 +37,13 @@ #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/prot.h" #include "libc/sysv/errfuns.h" +#include "libc/thread/posixthread.internal.h" #include "libc/thread/semaphore.h" #include "libc/thread/thread.h" #include "libc/thread/tls.h" static struct Semaphores { - pthread_once_t once; + atomic_uint once; pthread_mutex_t lock; struct Semaphore { struct Semaphore *next; @@ -49,27 +52,28 @@ static struct Semaphores { bool dead; int refs; } *list; -} g_semaphores; +} g_semaphores = { + .lock = PTHREAD_MUTEX_INITIALIZER, +}; static void sem_open_lock(void) { - pthread_mutex_lock(&g_semaphores.lock); + _pthread_mutex_lock(&g_semaphores.lock); } static void sem_open_unlock(void) { - pthread_mutex_unlock(&g_semaphores.lock); + _pthread_mutex_unlock(&g_semaphores.lock); } static void sem_open_wipe(void) { - pthread_mutex_init(&g_semaphores.lock, 0); + _pthread_mutex_wipe_np(&g_semaphores.lock); } static void sem_open_setup(void) { - sem_open_wipe(); pthread_atfork(sem_open_lock, sem_open_unlock, sem_open_wipe); } static void sem_open_init(void) { - pthread_once(&g_semaphores.once, sem_open_setup); + cosmo_once(&g_semaphores.once, sem_open_setup); } static sem_t *sem_open_impl(const char *path, int oflag, unsigned mode, diff --git a/libc/thread/sem_post.c b/libc/thread/sem_post.c index 80c164c9e..8da481cc0 100644 --- a/libc/thread/sem_post.c +++ b/libc/thread/sem_post.c @@ -19,6 +19,7 @@ #include "libc/assert.h" #include "libc/calls/calls.h" #include "libc/calls/syscall-sysv.internal.h" +#include "libc/cosmo.h" #include "libc/dce.h" #include "libc/errno.h" #include "libc/intrin/atomic.h" @@ -26,7 +27,6 @@ #include "libc/runtime/syslib.internal.h" #include "libc/sysv/errfuns.h" #include "libc/thread/semaphore.h" -#include "third_party/nsync/futex.internal.h" /** * Unlocks semaphore. @@ -46,7 +46,7 @@ int sem_post(sem_t *sem) { old = atomic_fetch_add_explicit(&sem->sem_value, 1, memory_order_acq_rel); unassert(old > INT_MIN); if (old >= 0) { - wakeups = nsync_futex_wake_(&sem->sem_value, 1, sem->sem_pshared); + wakeups = cosmo_futex_wake(&sem->sem_value, 1, sem->sem_pshared); npassert(wakeups >= 0); rc = 0; } else { diff --git a/libc/thread/sem_timedwait.c b/libc/thread/sem_timedwait.c index be046ce6e..b68193fe6 100644 --- a/libc/thread/sem_timedwait.c +++ b/libc/thread/sem_timedwait.c @@ -22,6 +22,7 @@ #include "libc/calls/struct/timespec.h" #include "libc/calls/struct/timespec.internal.h" #include "libc/calls/syscall-sysv.internal.h" +#include "libc/cosmo.h" #include "libc/dce.h" #include "libc/errno.h" #include "libc/intrin/atomic.h" @@ -32,7 +33,6 @@ #include "libc/sysv/errfuns.h" #include "libc/thread/semaphore.h" #include "libc/thread/thread.h" -#include "third_party/nsync/futex.internal.h" static void sem_delay(int n) { volatile int i; @@ -119,8 +119,8 @@ int sem_timedwait(sem_t *sem, const struct timespec *abstime) { do { if (!(v = atomic_load_explicit(&sem->sem_value, memory_order_relaxed))) { - rc = nsync_futex_wait_(&sem->sem_value, v, sem->sem_pshared, - CLOCK_REALTIME, abstime); + rc = cosmo_futex_wait(&sem->sem_value, v, sem->sem_pshared, + CLOCK_REALTIME, abstime); if (rc == -EINTR || rc == -ECANCELED) { errno = -rc; rc = -1; diff --git a/libc/thread/semaphore.h b/libc/thread/semaphore.h index 64119e2be..ee03fe926 100644 --- a/libc/thread/semaphore.h +++ b/libc/thread/semaphore.h @@ -34,10 +34,10 @@ typedef struct { int sem_init(sem_t *, int, unsigned) libcesque; int sem_destroy(sem_t *) libcesque; -int sem_post(sem_t *) libcesque; -int sem_wait(sem_t *) libcesque; -int sem_trywait(sem_t *) libcesque; -int sem_timedwait(sem_t *, const struct timespec *) libcesque; +int sem_post(sem_t *) dontthrow; +int sem_wait(sem_t *) dontthrow; +int sem_trywait(sem_t *) dontthrow; +int sem_timedwait(sem_t *, const struct timespec *) dontthrow; int sem_getvalue(sem_t *, int *) libcesque; sem_t *sem_open(const char *, int, ...) libcesque; int sem_close(sem_t *) libcesque; diff --git a/libc/thread/thread.h b/libc/thread/thread.h index 4f4cd3eb4..b7ac8a119 100644 --- a/libc/thread/thread.h +++ b/libc/thread/thread.h @@ -2,17 +2,19 @@ #define COSMOPOLITAN_LIBC_THREAD_THREAD_H_ #define PTHREAD_KEYS_MAX 46 -#define PTHREAD_STACK_MIN 65536 +#define PTHREAD_STACK_MIN 32768 #define PTHREAD_USE_NSYNC 1 #define PTHREAD_DESTRUCTOR_ITERATIONS 4 #define PTHREAD_BARRIER_SERIAL_THREAD 31337 -#define PTHREAD_MUTEX_NORMAL 0 -#define PTHREAD_MUTEX_RECURSIVE 1 -#define PTHREAD_MUTEX_ERRORCHECK 2 -#define PTHREAD_MUTEX_STALLED 0 -#define PTHREAD_MUTEX_ROBUST 1 +#define PTHREAD_MUTEX_DEFAULT 0 +#define PTHREAD_MUTEX_NORMAL 1 +#define PTHREAD_MUTEX_RECURSIVE 2 +#define PTHREAD_MUTEX_ERRORCHECK 3 + +#define PTHREAD_MUTEX_STALLED 0 +#define PTHREAD_MUTEX_ROBUST 2048 #define PTHREAD_PROCESS_PRIVATE 0 #define PTHREAD_PROCESS_SHARED 4 @@ -43,12 +45,12 @@ COSMOPOLITAN_C_START_ #define PTHREAD_ONCE_INIT {0} #define PTHREAD_COND_INITIALIZER {0} #define PTHREAD_RWLOCK_INITIALIZER {0} -#define PTHREAD_MUTEX_INITIALIZER {0} -#define PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP {0, {}, PTHREAD_MUTEX_RECURSIVE} - -#define PTHREAD_SIGNAL_SAFE_MUTEX_INITIALIZER_NP \ - {0, {}, PTHREAD_MUTEX_RECURSIVE | PTHREAD_PROCESS_SHARED} +#define PTHREAD_MUTEX_INITIALIZER {0, PTHREAD_MUTEX_DEFAULT} +#define PTHREAD_NORMAL_MUTEX_INITIALIZER_NP {0, PTHREAD_MUTEX_NORMAL} +#define PTHREAD_SHARED_MUTEX_INITIALIZER_NP {0, PTHREAD_PROCESS_SHARED} +#define PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP {0, PTHREAD_MUTEX_RECURSIVE} +#define PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP {0, PTHREAD_MUTEX_ERRORCHECK} #ifndef __cplusplus #define _PTHREAD_ATOMIC(x) _Atomic(x) @@ -72,14 +74,11 @@ typedef struct pthread_spinlock_s { } pthread_spinlock_t; typedef struct pthread_mutex_s { - uint32_t _nsync; - union { - int32_t _pid; - _PTHREAD_ATOMIC(int32_t) _futex; - }; - /* this cleverly overlaps with NSYNC struct Dll *waiters; */ + void *_edges; _PTHREAD_ATOMIC(uint64_t) _word; - long _nsyncx[2]; + _PTHREAD_ATOMIC(int) _futex; + int _pid; + void *_nsync[2]; } pthread_mutex_t; typedef struct pthread_mutexattr_s { @@ -92,23 +91,26 @@ typedef struct pthread_condattr_s { } pthread_condattr_t; typedef struct pthread_cond_s { - union { - void *_align; - struct { - uint32_t _nsync; - char _pshared; - char _clock; - char _footek; - _PTHREAD_ATOMIC(char) _waited; - }; - }; + char _pshared; + char _clock; + char _footek; + _PTHREAD_ATOMIC(char) _waited; _PTHREAD_ATOMIC(uint32_t) _sequence; _PTHREAD_ATOMIC(uint32_t) _waiters; + void *_nsync[2]; } pthread_cond_t; typedef struct pthread_rwlock_s { - void *_nsync[2]; - char _iswrite; + union { + void *_nsync[2]; + struct { + uint32_t _nsync_word; + char _pshared; + char _iswrite; + _PTHREAD_ATOMIC(uint32_t) _word; + _PTHREAD_ATOMIC(uint32_t) _waiters; + }; + }; } pthread_rwlock_t; typedef struct pthread_barrier_s { @@ -125,10 +127,10 @@ typedef struct pthread_attr_s { int __schedparam; int __schedpolicy; int __contentionscope; - int __guardsize; - int __stacksize; int __sigaltstacksize; uint64_t __sigmask; + size_t __guardsize; + size_t __stacksize; void *__stackaddr; void *__sigaltstackaddr; } pthread_attr_t; @@ -149,55 +151,58 @@ int pthread_attr_getguardsize(const pthread_attr_t *, size_t *) libcesque params int pthread_attr_getinheritsched(const pthread_attr_t *, int *) libcesque paramsnonnull(); int pthread_attr_getschedpolicy(const pthread_attr_t *, int *) libcesque paramsnonnull(); int pthread_attr_getscope(const pthread_attr_t *, int *) libcesque paramsnonnull(); -int pthread_attr_getstack(const pthread_attr_t *, void **, size_t *) libcesque paramsnonnull(); -int pthread_attr_getstacksize(const pthread_attr_t *, size_t *) libcesque paramsnonnull(); int pthread_attr_getsigaltstack_np(const pthread_attr_t *, void **, size_t *) libcesque paramsnonnull(); int pthread_attr_getsigaltstacksize_np(const pthread_attr_t *, size_t *) libcesque paramsnonnull(); +int pthread_attr_getstack(const pthread_attr_t *, void **, size_t *) libcesque paramsnonnull(); +int pthread_attr_getstacksize(const pthread_attr_t *, size_t *) libcesque paramsnonnull(); int pthread_attr_init(pthread_attr_t *) libcesque paramsnonnull(); int pthread_attr_setdetachstate(pthread_attr_t *, int) libcesque paramsnonnull(); int pthread_attr_setguardsize(pthread_attr_t *, size_t) libcesque paramsnonnull(); int pthread_attr_setinheritsched(pthread_attr_t *, int) libcesque paramsnonnull(); int pthread_attr_setschedpolicy(pthread_attr_t *, int) libcesque paramsnonnull(); int pthread_attr_setscope(pthread_attr_t *, int) libcesque paramsnonnull(); -int pthread_attr_setstack(pthread_attr_t *, void *, size_t) libcesque paramsnonnull((1)); -int pthread_attr_setstacksize(pthread_attr_t *, size_t) libcesque paramsnonnull(); int pthread_attr_setsigaltstack_np(pthread_attr_t *, void *, size_t) libcesque paramsnonnull((1)); int pthread_attr_setsigaltstacksize_np(pthread_attr_t *, size_t); +int pthread_attr_setstack(pthread_attr_t *, void *, size_t) libcesque paramsnonnull((1)); +int pthread_attr_setstacksize(pthread_attr_t *, size_t) libcesque paramsnonnull(); int pthread_barrier_destroy(pthread_barrier_t *) libcesque paramsnonnull(); int pthread_barrier_init(pthread_barrier_t *, const pthread_barrierattr_t *, unsigned) libcesque paramsnonnull((1)); -int pthread_barrier_wait(pthread_barrier_t *) libcesque paramsnonnull(); +int pthread_barrier_wait(pthread_barrier_t *) dontthrow paramsnonnull(); int pthread_barrierattr_destroy(pthread_barrierattr_t *) libcesque paramsnonnull(); int pthread_barrierattr_getpshared(const pthread_barrierattr_t *, int *) libcesque paramsnonnull(); int pthread_barrierattr_init(pthread_barrierattr_t *) libcesque paramsnonnull(); int pthread_barrierattr_setpshared(pthread_barrierattr_t *, int) libcesque paramsnonnull(); -int pthread_cancel(pthread_t) libcesque; -int pthread_cond_broadcast(pthread_cond_t *) libcesque paramsnonnull(); +int pthread_cancel(pthread_t) dontthrow; +int pthread_cond_broadcast(pthread_cond_t *) dontthrow paramsnonnull(); int pthread_cond_destroy(pthread_cond_t *) libcesque paramsnonnull(); int pthread_cond_init(pthread_cond_t *, const pthread_condattr_t *) libcesque paramsnonnull((1)); -int pthread_cond_signal(pthread_cond_t *) libcesque paramsnonnull(); -int pthread_cond_wait(pthread_cond_t *, pthread_mutex_t *) libcesque paramsnonnull(); -int pthread_condattr_init(pthread_condattr_t *) libcesque paramsnonnull(); +int pthread_cond_signal(pthread_cond_t *) dontthrow paramsnonnull(); +int pthread_cond_wait(pthread_cond_t *, pthread_mutex_t *) dontthrow paramsnonnull(); int pthread_condattr_destroy(pthread_condattr_t *) libcesque paramsnonnull(); -int pthread_condattr_setpshared(pthread_condattr_t *, int) libcesque paramsnonnull(); -int pthread_condattr_getpshared(const pthread_condattr_t *, int *) libcesque paramsnonnull(); -int pthread_condattr_setclock(pthread_condattr_t *, int) libcesque paramsnonnull(); int pthread_condattr_getclock(const pthread_condattr_t *, int *) libcesque paramsnonnull(); +int pthread_condattr_getpshared(const pthread_condattr_t *, int *) libcesque paramsnonnull(); +int pthread_condattr_init(pthread_condattr_t *) libcesque paramsnonnull(); +int pthread_condattr_setclock(pthread_condattr_t *, int) libcesque paramsnonnull(); +int pthread_condattr_setpshared(pthread_condattr_t *, int) libcesque paramsnonnull(); int pthread_create(pthread_t *, const pthread_attr_t *, void *(*)(void *), void *) dontthrow paramsnonnull((1)); -int pthread_detach(pthread_t) libcesque; +int pthread_decimate_np(void) dontthrow; +int pthread_delay_np(const void *, int) dontthrow; +int pthread_detach(pthread_t) dontthrow; int pthread_equal(pthread_t, pthread_t) libcesque; int pthread_getattr_np(pthread_t, pthread_attr_t *) libcesque paramsnonnull(); int pthread_getname_np(pthread_t, char *, size_t) libcesque paramsnonnull(); int pthread_getunique_np(pthread_t, pthread_id_np_t *) libcesque paramsnonnull(); -int pthread_join(pthread_t, void **) libcesque; +int pthread_join(pthread_t, void **) dontthrow; int pthread_key_create(pthread_key_t *, pthread_key_dtor) libcesque paramsnonnull((1)); int pthread_key_delete(pthread_key_t) libcesque; -int pthread_kill(pthread_t, int) libcesque; -int pthread_mutex_consistent(pthread_mutex_t *) libcesque paramsnonnull(); +int pthread_kill(pthread_t, int) dontthrow; +int pthread_mutex_consistent(pthread_mutex_t *) dontthrow paramsnonnull(); int pthread_mutex_destroy(pthread_mutex_t *) libcesque paramsnonnull(); int pthread_mutex_init(pthread_mutex_t *, const pthread_mutexattr_t *) libcesque paramsnonnull((1)); -int pthread_mutex_lock(pthread_mutex_t *) libcesque paramsnonnull(); -int pthread_mutex_trylock(pthread_mutex_t *) libcesque paramsnonnull(); -int pthread_mutex_unlock(pthread_mutex_t *) libcesque paramsnonnull(); +int pthread_mutex_lock(pthread_mutex_t *) dontthrow paramsnonnull(); +int pthread_mutex_trylock(pthread_mutex_t *) dontthrow paramsnonnull(); +int pthread_mutex_unlock(pthread_mutex_t *) dontthrow paramsnonnull(); +int pthread_mutex_wipe_np(pthread_mutex_t *) libcesque paramsnonnull(); int pthread_mutexattr_destroy(pthread_mutexattr_t *) libcesque paramsnonnull(); int pthread_mutexattr_getpshared(const pthread_mutexattr_t *, int *) libcesque paramsnonnull(); int pthread_mutexattr_gettype(const pthread_mutexattr_t *, int *) libcesque paramsnonnull(); @@ -206,14 +211,13 @@ int pthread_mutexattr_setpshared(pthread_mutexattr_t *, int) libcesque paramsnon int pthread_mutexattr_settype(pthread_mutexattr_t *, int) libcesque paramsnonnull(); int pthread_once(pthread_once_t *, void (*)(void)) paramsnonnull(); int pthread_orphan_np(void) libcesque; -int pthread_decimate_np(void) libcesque; int pthread_rwlock_destroy(pthread_rwlock_t *) libcesque paramsnonnull(); int pthread_rwlock_init(pthread_rwlock_t *, const pthread_rwlockattr_t *) libcesque paramsnonnull((1)); -int pthread_rwlock_rdlock(pthread_rwlock_t *) libcesque paramsnonnull(); -int pthread_rwlock_tryrdlock(pthread_rwlock_t *) libcesque paramsnonnull(); -int pthread_rwlock_trywrlock(pthread_rwlock_t *) libcesque paramsnonnull(); -int pthread_rwlock_unlock(pthread_rwlock_t *) libcesque paramsnonnull(); -int pthread_rwlock_wrlock(pthread_rwlock_t *) libcesque paramsnonnull(); +int pthread_rwlock_rdlock(pthread_rwlock_t *) dontthrow paramsnonnull(); +int pthread_rwlock_tryrdlock(pthread_rwlock_t *) dontthrow paramsnonnull(); +int pthread_rwlock_trywrlock(pthread_rwlock_t *) dontthrow paramsnonnull(); +int pthread_rwlock_unlock(pthread_rwlock_t *) dontthrow paramsnonnull(); +int pthread_rwlock_wrlock(pthread_rwlock_t *) dontthrow paramsnonnull(); int pthread_rwlockattr_destroy(pthread_rwlockattr_t *) libcesque paramsnonnull(); int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *, int *) libcesque paramsnonnull(); int pthread_rwlockattr_init(pthread_rwlockattr_t *) libcesque paramsnonnull(); @@ -225,22 +229,21 @@ int pthread_setschedprio(pthread_t, int) libcesque; int pthread_setspecific(pthread_key_t, const void *) libcesque; int pthread_spin_destroy(pthread_spinlock_t *) libcesque paramsnonnull(); int pthread_spin_init(pthread_spinlock_t *, int) libcesque paramsnonnull(); -int pthread_spin_lock(pthread_spinlock_t *) libcesque paramsnonnull(); -int pthread_spin_trylock(pthread_spinlock_t *) libcesque paramsnonnull(); -int pthread_spin_unlock(pthread_spinlock_t *) libcesque paramsnonnull(); -int pthread_testcancel_np(void) libcesque; -int pthread_tryjoin_np(pthread_t, void **) libcesque; -int pthread_delay_np(const void *, int) libcesque; -int pthread_yield_np(void) libcesque; -int pthread_yield(void) libcesque; +int pthread_spin_lock(pthread_spinlock_t *) dontthrow paramsnonnull(); +int pthread_spin_trylock(pthread_spinlock_t *) dontthrow paramsnonnull(); +int pthread_spin_unlock(pthread_spinlock_t *) dontthrow paramsnonnull(); +int pthread_testcancel_np(void) dontthrow; +int pthread_tryjoin_np(pthread_t, void **) dontthrow; +int pthread_yield(void) dontthrow; +int pthread_yield_np(void) dontthrow; pthread_id_np_t pthread_getthreadid_np(void) libcesque; pthread_t pthread_self(void) libcesque pureconst; void *pthread_getspecific(pthread_key_t) libcesque; -void pthread_cleanup_pop(struct _pthread_cleanup_buffer *, int) libcesque paramsnonnull(); -void pthread_cleanup_push(struct _pthread_cleanup_buffer *, void (*)(void *), void *) libcesque paramsnonnull((1)); -void pthread_exit(void *) libcesque wontreturn; -void pthread_testcancel(void) libcesque; -void pthread_pause_np(void) libcesque; +void pthread_cleanup_pop(struct _pthread_cleanup_buffer *, int) dontthrow paramsnonnull(); +void pthread_cleanup_push(struct _pthread_cleanup_buffer *, void (*)(void *), void *) dontthrow paramsnonnull((1)); +void pthread_exit(void *) wontreturn; +void pthread_pause_np(void) dontthrow; +void pthread_testcancel(void) dontthrow; /* clang-format on */ diff --git a/libc/thread/thread2.h b/libc/thread/thread2.h index db1d845ab..a51e48ce2 100644 --- a/libc/thread/thread2.h +++ b/libc/thread/thread2.h @@ -13,12 +13,12 @@ int pthread_attr_getschedparam(const pthread_attr_t *, struct sched_param *) lib int pthread_attr_getsigmask_np(const pthread_attr_t *, sigset_t *) libcesque paramsnonnull((1)); int pthread_attr_setschedparam(pthread_attr_t *, const struct sched_param *) libcesque paramsnonnull(); int pthread_attr_setsigmask_np(pthread_attr_t *, const sigset_t *) libcesque paramsnonnull((1)); -int pthread_cond_timedwait(pthread_cond_t *, pthread_mutex_t *, const struct timespec *) libcesque paramsnonnull((1, 2)); +int pthread_cond_timedwait(pthread_cond_t *, pthread_mutex_t *, const struct timespec *) dontthrow paramsnonnull((1, 2)); int pthread_getaffinity_np(pthread_t, size_t, cpu_set_t *) libcesque paramsnonnull(); int pthread_getschedparam(pthread_t, int *, struct sched_param *) libcesque paramsnonnull(); int pthread_setaffinity_np(pthread_t, size_t, const cpu_set_t *) libcesque paramsnonnull(); int pthread_setschedparam(pthread_t, int, const struct sched_param *) libcesque paramsnonnull(); -int pthread_timedjoin_np(pthread_t, void **, struct timespec *) libcesque; +int pthread_timedjoin_np(pthread_t, void **, struct timespec *) dontthrow; /* clang-format off */ COSMOPOLITAN_C_END_ diff --git a/libc/thread/tls.h b/libc/thread/tls.h index 6c8be5747..e4c2a73b1 100644 --- a/libc/thread/tls.h +++ b/libc/thread/tls.h @@ -10,12 +10,11 @@ COSMOPOLITAN_C_START_ struct CosmoFtrace { /* 16 */ char ft_once; /* 0 */ - char ft_noreentry; /* 1 */ int ft_skew; /* 4 */ int64_t ft_lastaddr; /* 8 */ }; -/* cosmopolitan thread information block (512 bytes) */ +/* cosmopolitan thread information block (1024 bytes) */ /* NOTE: update aarch64 libc/errno.h if sizeof changes */ /* NOTE: update aarch64 libc/proc/vfork.S if sizeof changes */ /* NOTE: update aarch64 libc/nexgen32e/gc.S if sizeof changes */ @@ -23,10 +22,10 @@ struct CosmoTib { struct CosmoTib *tib_self; /* 0x00 */ struct CosmoFtrace tib_ftracer; /* 0x08 */ void *tib_garbages; /* 0x18 */ - intptr_t __unused; /* 0x20 */ + _Atomic(int32_t) tib_ptid; /* 0x20 transitions 0 → tid */ intptr_t tib_pthread; /* 0x28 */ struct CosmoTib *tib_self2; /* 0x30 */ - _Atomic(int32_t) tib_tid; /* 0x38 transitions -1 → tid → 0 */ + _Atomic(int32_t) tib_ctid; /* 0x38 transitions -1 → tid → 0 */ int32_t tib_errno; /* 0x3c */ uint64_t tib_flags; /* 0x40 */ int tib_ftrace; /* inherited */ @@ -40,6 +39,7 @@ struct CosmoTib { void *tib_nsync; void *tib_atexit; _Atomic(void *) tib_keys[46]; + void *tib_locks[64]; } __attribute__((__aligned__(64))); extern char __tls_morphed; @@ -78,6 +78,10 @@ forceinline pureconst struct CosmoTib *__get_tls(void) { #endif } +struct CosmoTib *__get_tls_privileged(void) dontthrow pureconst; +struct CosmoTib *__get_tls_win32(void) dontthrow; +void __set_tls_win32(void *) libcesque; + #ifdef __x86_64__ #define __adj_tls(tib) (tib) #elif defined(__aarch64__) diff --git a/libc/thread/tls2.internal.h b/libc/thread/tls2.internal.h deleted file mode 100644 index be2e1c02a..000000000 --- a/libc/thread/tls2.internal.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_THREAD_TLS2_H_ -#define COSMOPOLITAN_LIBC_THREAD_TLS2_H_ -#include "libc/dce.h" -#include "libc/thread/tls.h" -COSMOPOLITAN_C_START_ -#if defined(__GNUC__) && defined(__x86_64__) - -/** - * Returns location of thread information block. - * - * This should be favored over __get_tls() for .privileged code that - * can't be self-modified by __enable_tls(). - */ -forceinline struct CosmoTib *__get_tls_privileged(void) { - char *tib, *lin = (char *)0x30; - if (IsNetbsd() || IsOpenbsd()) { - __asm__("mov\t%%fs:(%1),%0" : "=a"(tib) : "r"(lin) : "memory"); - } else { - __asm__("mov\t%%gs:(%1),%0" : "=a"(tib) : "r"(lin) : "memory"); - if (IsWindows()) - tib = *(char **)(tib + 0x1480 + __tls_index * 8); - } - return (struct CosmoTib *)tib; -} - -forceinline struct CosmoTib *__get_tls_win32(void) { - char *tib, *lin = (char *)0x30; - __asm__("mov\t%%gs:(%1),%0" : "=a"(tib) : "r"(lin) : "memory"); - tib = *(char **)(tib + 0x1480 + __tls_index * 8); - return (struct CosmoTib *)tib; -} - -forceinline void __set_tls_win32(void *tls) { - __asm__("mov\t%1,%%gs:%0" : "=m"(*((long *)0x1480 + __tls_index)) : "r"(tls)); -} - -#elif defined(__aarch64__) -#define __get_tls_privileged() __get_tls() -#define __get_tls_win32() ((struct CosmoTib *)0) -#define __set_tls_win32(tls) (void)0 -#endif /* GNU x86-64 */ -COSMOPOLITAN_C_END_ -#endif /* COSMOPOLITAN_LIBC_THREAD_TLS2_H_ */ diff --git a/libc/vga/tty.greg.c b/libc/vga/tty.greg.c index ad1f009d7..7b2738a3b 100644 --- a/libc/vga/tty.greg.c +++ b/libc/vga/tty.greg.c @@ -167,7 +167,6 @@ void _StartTty(struct Tty *tty, unsigned char type, unsigned short yp, unsigned short startx, unsigned char yc, unsigned char xc, void *fb, unsigned init_flags) { unsigned short yn, xn, xs = xp * sizeof(TtyCanvasColor); - struct DirectMap dm; bzero(tty, sizeof(struct Tty)); SetYp(tty, yp); SetXp(tty, xp); @@ -183,9 +182,9 @@ void _StartTty(struct Tty *tty, unsigned char type, unsigned short yp, tty->canvas = fb; xs = xsfb; } else { - dm = sys_mmap_metal(NULL, (size_t)yp * xs, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (dm.addr == (void *)-1) { + void *addr = sys_mmap_metal(NULL, (size_t)yp * xs, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (addr == (void *)-1) { /* * We are a bit low on memory. Try to go on anyway, & initialize * our tty as an emergency console. @@ -194,7 +193,7 @@ void _StartTty(struct Tty *tty, unsigned char type, unsigned short yp, tty->canvas = fb; xs = xsfb; } else - tty->canvas = dm.addr; + tty->canvas = addr; } } SetYn(tty, yn); diff --git a/net/turfwar/BUILD.mk b/net/turfwar/BUILD.mk index 73a1fd2e6..0a49ad12f 100644 --- a/net/turfwar/BUILD.mk +++ b/net/turfwar/BUILD.mk @@ -28,6 +28,7 @@ NET_TURFWAR_DIRECTDEPS = \ LIBC_SOCK \ LIBC_STDIO \ LIBC_STR \ + LIBC_SYSTEM \ LIBC_SYSV \ LIBC_THREAD \ LIBC_X \ @@ -39,7 +40,7 @@ NET_TURFWAR_DIRECTDEPS = \ THIRD_PARTY_SQLITE3 \ THIRD_PARTY_STB \ THIRD_PARTY_TZ \ - THIRD_PARTY_ZLIB + THIRD_PARTY_ZLIB \ NET_TURFWAR_DEPS := \ $(call uniq,$(foreach x,$(NET_TURFWAR_DIRECTDEPS),$($(x)))) diff --git a/test/ctl/BUILD.mk b/test/ctl/BUILD.mk index 6b36e766d..a6944bbf8 100644 --- a/test/ctl/BUILD.mk +++ b/test/ctl/BUILD.mk @@ -20,7 +20,7 @@ TEST_CTL_DIRECTDEPS = \ LIBC_NEXGEN32E \ LIBC_PROC \ LIBC_STDIO \ - LIBC_STDIO \ + LIBC_SYSTEM \ LIBC_THREAD \ THIRD_PARTY_LIBCXX \ THIRD_PARTY_LIBCXXABI \ diff --git a/test/ctl/shared_ptr_test.cc b/test/ctl/shared_ptr_test.cc index c910ddf70..f9a8dd597 100644 --- a/test/ctl/shared_ptr_test.cc +++ b/test/ctl/shared_ptr_test.cc @@ -16,6 +16,7 @@ // TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. +#include "ctl/is_same.h" #include "ctl/shared_ptr.h" #include "ctl/vector.h" #include "libc/mem/leaks.h" @@ -88,6 +89,21 @@ class SharedThis : public enable_shared_from_this class CanShareThis : public enable_shared_from_this {}; +// Sample class used to demonstrate the CTL shared_ptr's weak_self feature. +struct Tree : ctl::weak_self_base +{ + ctl::shared_ptr l, r; + ctl::weak_ptr p; + Tree(ctl::weak_ptr const& self, auto&& l2, auto&& r2) + : l(ctl::forward(l2)), r(ctl::forward(r2)) + { + if (l) + l->p = self; + if (r) + r->p = self; + } +}; + int main() { @@ -276,6 +292,19 @@ main() return 25; } + if constexpr (ctl::is_same_v, ctl::shared_ptr>) { + // Exercise our off-STL make_shared with weak self support. + auto t = ctl::make_shared( + ctl::make_shared(ctl::make_shared(nullptr, nullptr), + nullptr), + ctl::make_shared(nullptr, nullptr)); + auto t2 = t->l->l->p.lock()->p.lock(); + if (t.owner_before(t2) || t2.owner_before(t)) + return 26; + if (!t.owner_before(t->l) && !t->l.owner_before(t)) + return 27; + } + // TODO(mrdomino): exercise threads / races. The reference count should be // atomically maintained. diff --git a/test/ctl/string_test.cc b/test/ctl/string_test.cc index 90b19e9c8..6319a82da 100644 --- a/test/ctl/string_test.cc +++ b/test/ctl/string_test.cc @@ -404,5 +404,69 @@ main() } } + { + String s = "love"; + String b; + b.append(s, 1, 2); + if (b != "ov") + return 107; + } + + { + String s = "ee"; + if (s.find_last_of('E') != String::npos) + return 108; + if (s.find_last_of('e') != 1) + return 109; + } + + { + String e = ""; + String s = "ee"; + if (e.find_last_of("") != String::npos) + return 110; + if (s.find_last_of("") != String::npos) + return 111; + if (s.find_last_of("AE") != String::npos) + return 112; + if (s.find_last_of("ae") != 1) + return 113; + if (s.find_last_of("ae", 1) != 1) + return 114; + if (s.find_last_of("ae", 0) != 0) + return 115; + if (s.find_last_of("ae", 10) != 1) + return 116; + } + + { + String s = "ee"; + if (s.find_first_of('E') != String::npos) + return 117; + if (s.find_first_of('e') != 0) + return 118; + if (s.find_first_of('e', 1) != 1) + return 119; + } + + { + String e = ""; + String s = "ee"; + if (e.find_first_of("") != String::npos) + return 120; + if (s.find_first_of("") != String::npos) + return 121; + if (s.find_first_of("AE") != String::npos) + return 122; + if (s.find_first_of("ae") != 0) + return 123; + if (s.find_first_of("ae", 1) != 1) + return 124; + if (s.find_first_of("ae", 0) != 0) + return 125; + if (s.find_first_of("ae", 10) != String::npos) + return 126; + } + CheckForMemoryLeaks(); } diff --git a/test/ctl/string_view_test.cc b/test/ctl/string_view_test.cc index a82743e15..a371fdfb5 100644 --- a/test/ctl/string_view_test.cc +++ b/test/ctl/string_view_test.cc @@ -16,6 +16,7 @@ // TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. +#include "ctl/string.h" #include "ctl/string_view.h" #include "libc/mem/leaks.h" #include "libc/str/str.h" @@ -172,5 +173,70 @@ main(int argc, char* argv[]) return 32; } + { + ctl::string b; + const ctl::string_view s = "hi"; + for (char c : s) + b += c; + if (b != "hi") + return 2; + } + + { + ctl::string_view s = "ee"; + if (s.find_last_of('E') != ctl::string_view::npos) + return 108; + if (s.find_last_of('e') != 1) + return 109; + } + + { + ctl::string_view e = ""; + ctl::string_view s = "ee"; + if (e.find_last_of("") != ctl::string_view::npos) + return 110; + if (s.find_last_of("") != ctl::string_view::npos) + return 111; + if (s.find_last_of("AE") != ctl::string_view::npos) + return 112; + if (s.find_last_of("ae") != 1) + return 113; + if (s.find_last_of("ae", 1) != 1) + return 114; + if (s.find_last_of("ae", 0) != 0) + return 115; + if (s.find_last_of("ae", 10) != 1) + return 116; + } + + { + ctl::string_view s = "ee"; + if (s.find_first_of('E') != ctl::string_view::npos) + return 117; + if (s.find_first_of('e') != 0) + return 118; + if (s.find_first_of('e', 1) != 1) + return 119; + } + + { + ctl::string_view e = ""; + ctl::string_view s = "ee"; + if (e.find_first_of("") != ctl::string_view::npos) + return 120; + if (s.find_first_of("") != ctl::string_view::npos) + return 121; + if (s.find_first_of("AE") != ctl::string_view::npos) + return 122; + if (s.find_first_of("ae") != 0) + return 123; + if (s.find_first_of("ae", 1) != 1) + return 124; + if (s.find_first_of("ae", 0) != 0) + return 125; + if (s.find_first_of("ae", 10) != ctl::string_view::npos) + return 126; + } + CheckForMemoryLeaks(); } diff --git a/test/libc/calls/cachestat_test.c b/test/libc/calls/cachestat_test.c index b756d852d..8f91781f6 100644 --- a/test/libc/calls/cachestat_test.c +++ b/test/libc/calls/cachestat_test.c @@ -29,6 +29,7 @@ #include "libc/runtime/runtime.h" #include "libc/runtime/sysconf.h" #include "libc/stdio/rand.h" +#include "libc/stdio/stdio.h" #include "libc/sysv/consts/auxv.h" #include "libc/sysv/consts/o.h" #include "libc/testlib/testlib.h" @@ -50,6 +51,9 @@ void SetUpOnce(void) { // ASSERT_SYS(0, 0, pledge("stdio rpath wpath cpath", 0)); } +// TODO(jart): fix this test +#if 0 + TEST(cachestat, testCachestatOnDevices) { const char *const files[] = { "/dev/zero", "/dev/null", "/dev/urandom", "/proc/version", "/proc", @@ -63,6 +67,8 @@ TEST(cachestat, testCachestatOnDevices) { } } +#endif + TEST(cachestat, testCachestatAfterWrite) { size_t size = 4 * pagesize; char *data = gc(xmalloc(size)); @@ -104,12 +110,14 @@ done: } TEST(cachestat, testCachestatShmem) { + char name[64]; + sprintf(name, "/cachestat_test-%ld", _rand64()); size_t filesize = 512 * 2 * pagesize; // 2 2MB huge pages. size_t compute_len = 512 * pagesize; unsigned long num_pages = compute_len / pagesize; char *data = gc(xmalloc(filesize)); ASSERT_SYS(0, filesize, getrandom(data, filesize, 0)); - ASSERT_SYS(0, 3, shm_open("tmpshmcstat", O_CREAT | O_RDWR, 0600)); + ASSERT_SYS(0, 3, shm_open(name, O_CREAT | O_RDWR, 0600)); ASSERT_SYS(0, 0, ftruncate(3, filesize)); ASSERT_SYS(0, filesize, write(3, data, filesize)); struct cachestat_range range = {pagesize, compute_len}; @@ -117,6 +125,6 @@ TEST(cachestat, testCachestatShmem) { ASSERT_SYS(0, 0, cachestat(3, &range, &cs, 0)); ASSERT_EQ(num_pages, cs.nr_cache + cs.nr_evicted, "total number of cached and evicted pages is off.\n"); - ASSERT_SYS(0, 0, shm_unlink("tmpshmcstat")); + ASSERT_SYS(0, 0, shm_unlink(name)); ASSERT_SYS(0, 0, close(3)); } diff --git a/test/libc/calls/dup_test.c b/test/libc/calls/dup_test.c index cad66f18e..9421fecbb 100644 --- a/test/libc/calls/dup_test.c +++ b/test/libc/calls/dup_test.c @@ -94,7 +94,6 @@ TEST(dup2, zipossrc) { ASSERT_SYS(0, 0, close(3)); } -#ifdef __x86_64__ TEST(dup, clearsCloexecFlag) { static bool once; int ws; @@ -112,4 +111,3 @@ TEST(dup, clearsCloexecFlag) { ASSERT_EQ(72 << 8, ws); ASSERT_SYS(0, 0, close(3)); } -#endif diff --git a/test/libc/calls/getcontext_test.c b/test/libc/calls/getcontext_test.c index 35a9db833..c80219c7f 100644 --- a/test/libc/calls/getcontext_test.c +++ b/test/libc/calls/getcontext_test.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/calls/struct/sigset.h" +#include "libc/calls/struct/ucontext.internal.h" #include "libc/calls/ucontext.h" #include "libc/runtime/runtime.h" #include "libc/sysv/consts/sig.h" @@ -60,6 +61,7 @@ TEST(getcontext, canReadAndWriteSignalMask) { ASSERT_EQ(0, getcontext(&context)); if (!n) { n = 1; + context.uc_mcontext.RES0 = 0; ASSERT_TRUE(sigismember(&context.uc_sigmask, SIGUSR1)); sigaddset(&context.uc_sigmask, SIGUSR2); setcontext(&context); diff --git a/test/libc/calls/pledge_test.c b/test/libc/calls/pledge_test.c index 71d600834..089d965ef 100644 --- a/test/libc/calls/pledge_test.c +++ b/test/libc/calls/pledge_test.c @@ -64,6 +64,14 @@ void SetUpOnce(void) { testlib_enable_tmp_setup_teardown(); + if (pledge(0, 0) == -1) { + fprintf(stderr, "warning: pledge() not supported on this system %m\n"); + exit(0); + } +} + +void SetUp(void) { + __pledge_mode = PLEDGE_PENALTY_RETURN_EPERM; } void OnSig(int sig) { @@ -72,16 +80,6 @@ void OnSig(int sig) { int sys_memfd_secret(unsigned int); // our ENOSYS threshold -void SetUp(void) { - if (pledge(0, 0) == -1) { - fprintf(stderr, "warning: pledge() not supported on this system %m\n"); - exit(0); - } - testlib_extract("/zip/life.elf", "life.elf", 0755); - testlib_extract("/zip/sock.elf", "sock.elf", 0755); - __pledge_mode = PLEDGE_PENALTY_RETURN_EPERM; -} - TEST(pledge, default_allowsExit) { int *job; int ws, pid; @@ -107,6 +105,7 @@ TEST(pledge, execpromises_notok) { if (IsOpenbsd()) return; // b/c testing linux bpf int ws, pid; + testlib_extract("/zip/sock.elf", "sock.elf", 0755); ASSERT_NE(-1, (pid = fork())); if (!pid) { putenv("COMDBG=REDACTED"); @@ -532,6 +531,7 @@ TEST(pledge, open_cpath) { TEST(pledge, execpromises_ok) { if (IsOpenbsd()) return; // b/c testing linux bpf + testlib_extract("/zip/life.elf", "life.elf", 0755); int ws, pid; ASSERT_NE(-1, (pid = fork())); if (!pid) { @@ -549,6 +549,7 @@ TEST(pledge, execpromises_notok1) { if (IsOpenbsd()) return; // b/c testing linux bpf int ws, pid; + testlib_extract("/zip/sock.elf", "sock.elf", 0755); ASSERT_NE(-1, (pid = fork())); if (!pid) { putenv("COMDBG=REDACTED"); @@ -565,6 +566,7 @@ TEST(pledge, execpromises_reducesAtExecOnLinux) { if (IsOpenbsd()) return; // b/c testing linux bpf int ws, pid; + testlib_extract("/zip/sock.elf", "sock.elf", 0755); ASSERT_NE(-1, (pid = fork())); if (!pid) { putenv("COMDBG=REDACTED"); @@ -583,6 +585,7 @@ TEST(pledge_openbsd, execpromisesIsNull_letsItDoAnything) { if (!IsOpenbsd()) return; int ws, pid; + testlib_extract("/zip/sock.elf", "sock.elf", 0755); ASSERT_NE(-1, (pid = fork())); if (!pid) { ASSERT_SYS(0, 0, pledge("stdio exec", 0)); @@ -602,6 +605,7 @@ TEST(pledge_openbsd, execpromisesIsSuperset_letsItDoAnything) { if (!IsOpenbsd()) return; int ws, pid; + testlib_extract("/zip/sock.elf", "sock.elf", 0755); ASSERT_NE(-1, (pid = fork())); if (!pid) { ASSERT_SYS(0, 0, pledge("stdio rpath exec", "stdio rpath tty inet")); @@ -623,6 +627,7 @@ TEST(pledge_openbsd, execpromises_notok) { if (IsOpenbsd()) return; // mimmutable() ugh int ws, pid; + testlib_extract("/zip/sock.elf", "sock.elf", 0755); ASSERT_NE(-1, (pid = fork())); if (!pid) { putenv("COMDBG=REDACTED"); diff --git a/test/libc/calls/raise_test.c b/test/libc/calls/raise_test.c index 5ebb8189a..481f207c3 100644 --- a/test/libc/calls/raise_test.c +++ b/test/libc/calls/raise_test.c @@ -20,6 +20,7 @@ #include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/siginfo.h" #include "libc/dce.h" +#include "libc/mem/leaks.h" #include "libc/runtime/runtime.h" #include "libc/sysv/consts/sa.h" #include "libc/sysv/consts/sicode.h" @@ -30,6 +31,7 @@ #include "libc/thread/thread.h" TEST(raise, trap) { + AssertNoLocksAreHeld(); signal(SIGTRAP, SIG_DFL); SPAWN(fork); raise(SIGTRAP); @@ -44,6 +46,7 @@ TEST(raise, fpe) { } TEST(raise, usr1) { + AssertNoLocksAreHeld(); SPAWN(fork); raise(SIGUSR1); TERMS(SIGUSR1); @@ -53,9 +56,8 @@ int threadid; void WorkerQuit(int sig, siginfo_t *si, void *ctx) { ASSERT_EQ(SIGILL, sig); - if (!IsXnu() && !IsOpenbsd()) { + if (!IsXnu() && !IsOpenbsd()) ASSERT_EQ(SI_TKILL, si->si_code); - } ASSERT_EQ(threadid, gettid()); } @@ -69,6 +71,7 @@ void *Worker(void *arg) { TEST(raise, threaded) { SPAWN(fork); + AssertNoLocksAreHeld(); signal(SIGILL, SIG_DFL); pthread_t worker; ASSERT_EQ(0, pthread_create(&worker, 0, Worker, 0)); diff --git a/test/libc/calls/setrlimit_test.c b/test/libc/calls/setrlimit_test.c deleted file mode 100644 index 7f840519d..000000000 --- a/test/libc/calls/setrlimit_test.c +++ /dev/null @@ -1,247 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│ vi: set et 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 "dsp/core/core.h" -#include "libc/calls/calls.h" -#include "libc/calls/struct/rlimit.h" -#include "libc/calls/struct/timespec.h" -#include "libc/dce.h" -#include "libc/errno.h" -#include "libc/intrin/directmap.h" -#include "libc/intrin/safemacros.h" -#include "libc/limits.h" -#include "libc/runtime/runtime.h" -#include "libc/stdio/rand.h" -#include "libc/stdio/stdio.h" -#include "libc/sysv/consts/auxv.h" -#include "libc/sysv/consts/map.h" -#include "libc/sysv/consts/o.h" -#include "libc/sysv/consts/prot.h" -#include "libc/sysv/consts/rlimit.h" -#include "libc/sysv/consts/sig.h" -#include "libc/testlib/testlib.h" -#include "libc/time.h" -#include "libc/x/xsigaction.h" -#include "libc/x/xspawn.h" - -#ifdef __x86_64__ - -#define MEM (64 * 1024 * 1024) - -static char tmpname[PATH_MAX]; - -void OnSigxcpu(int sig) { - ASSERT_EQ(SIGXCPU, sig); - _Exit(0); -} - -void OnSigxfsz(int sig) { - unlink(tmpname); - ASSERT_EQ(SIGXFSZ, sig); - _Exit(0); -} - -TEST(setrlimit, testCpuLimit) { - int wstatus; - struct rlimit rlim; - struct timespec start; - double matrices[3][3][3]; - if (IsWindows()) - return; // of course it doesn't work on windows - if (IsXnu()) - return; // TODO(jart): it worked before - if (IsOpenbsd()) - return; // TODO(jart): fix flake - ASSERT_NE(-1, (wstatus = xspawn(0))); - if (wstatus == -2) { - ASSERT_EQ(0, xsigaction(SIGXCPU, OnSigxcpu, 0, 0, 0)); - ASSERT_EQ(0, getrlimit(RLIMIT_CPU, &rlim)); - rlim.rlim_cur = 1; // set soft limit to one second - ASSERT_EQ(0, setrlimit(RLIMIT_CPU, &rlim)); - start = timespec_real(); - do { - matmul3(matrices[0], matrices[1], matrices[2]); - matmul3(matrices[0], matrices[1], matrices[2]); - matmul3(matrices[0], matrices[1], matrices[2]); - matmul3(matrices[0], matrices[1], matrices[2]); - } while (timespec_sub(timespec_real(), start).tv_sec < 5); - _Exit(1); - } - EXPECT_TRUE(WIFEXITED(wstatus)); - EXPECT_FALSE(WIFSIGNALED(wstatus)); - EXPECT_EQ(0, WEXITSTATUS(wstatus)); - EXPECT_EQ(0, WTERMSIG(wstatus)); -} - -TEST(setrlimit, testFileSizeLimit) { - char junkdata[512]; - int i, fd, wstatus; - struct rlimit rlim; - if (IsWindows()) - return; /* of course it doesn't work on windows */ - ASSERT_NE(-1, (wstatus = xspawn(0))); - if (wstatus == -2) { - ASSERT_EQ(0, xsigaction(SIGXFSZ, OnSigxfsz, 0, 0, 0)); - ASSERT_EQ(0, getrlimit(RLIMIT_FSIZE, &rlim)); - rlim.rlim_cur = 1024 * 1024; /* set soft limit to one megabyte */ - ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rlim)); - snprintf(tmpname, sizeof(tmpname), "%s/%s.%d", - firstnonnull(getenv("TMPDIR"), "/tmp"), - firstnonnull(program_invocation_short_name, "unknown"), getpid()); - ASSERT_NE(-1, (fd = open(tmpname, O_RDWR | O_CREAT | O_TRUNC, 0644))); - rngset(junkdata, 512, _rand64, -1); - for (i = 0; i < 5 * 1024 * 1024 / 512; ++i) { - ASSERT_EQ(512, write(fd, junkdata, 512)); - } - close(fd); - unlink(tmpname); - _Exit(1); - } - EXPECT_TRUE(WIFEXITED(wstatus)); - EXPECT_FALSE(WIFSIGNALED(wstatus)); - EXPECT_EQ(0, WEXITSTATUS(wstatus)); - EXPECT_EQ(0, WTERMSIG(wstatus)); -} - -int SetMemoryLimit(size_t n) { - struct rlimit rlim = {0}; - getrlimit(RLIMIT_AS, &rlim); - rlim.rlim_cur = n; - rlim.rlim_max = n; - return setrlimit(RLIMIT_AS, &rlim); -} - -TEST(setrlimit, testMemoryLimit) { - char *p; - bool gotsome; - int i, wstatus; - ASSERT_NE(-1, (wstatus = xspawn(0))); - if (wstatus == -2) { - ASSERT_EQ(0, SetMemoryLimit(MEM)); - for (gotsome = false, i = 0; i < (MEM * 2) / getpagesize(); ++i) { - p = mmap(0, getpagesize(), PROT_READ | PROT_WRITE, - MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - if (p != MAP_FAILED) { - gotsome = true; - } else { - ASSERT_TRUE(gotsome); - ASSERT_EQ(ENOMEM, errno); - _Exit(0); - } - rngset(p, getpagesize(), _rand64, -1); - } - _Exit(1); - } - EXPECT_TRUE(WIFEXITED(wstatus)); - EXPECT_FALSE(WIFSIGNALED(wstatus)); - EXPECT_EQ(0, WEXITSTATUS(wstatus)); - EXPECT_EQ(0, WTERMSIG(wstatus)); -} - -TEST(setrlimit, testVirtualMemoryLimit) { - char *p; - int i, wstatus; - ASSERT_NE(-1, (wstatus = xspawn(0))); - if (wstatus == -2) { - ASSERT_EQ(0, setrlimit(RLIMIT_AS, &(struct rlimit){MEM, MEM})); - for (i = 0; i < (MEM * 2) / getpagesize(); ++i) { - p = sys_mmap(0, getpagesize(), PROT_READ | PROT_WRITE, - MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE, -1, 0) - .addr; - if (p == MAP_FAILED) { - ASSERT_EQ(ENOMEM, errno); - _Exit(0); - } - rngset(p, getpagesize(), _rand64, -1); - } - _Exit(1); - } - EXPECT_TRUE(WIFEXITED(wstatus)); - EXPECT_FALSE(WIFSIGNALED(wstatus)); - EXPECT_EQ(0, WEXITSTATUS(wstatus)); - EXPECT_EQ(0, WTERMSIG(wstatus)); -} - -TEST(setrlimit, testDataMemoryLimit) { - char *p; - int i, wstatus; - if (IsXnu()) - return; /* doesn't work on darwin */ - if (IsNetbsd()) - return; /* doesn't work on netbsd */ - if (IsFreebsd()) - return; /* doesn't work on freebsd */ - if (IsLinux()) - return; /* doesn't work on gnu/systemd */ - if (IsWindows()) - return; /* of course it doesn't work on windows */ - ASSERT_NE(-1, (wstatus = xspawn(0))); - if (wstatus == -2) { - ASSERT_EQ(0, setrlimit(RLIMIT_DATA, &(struct rlimit){MEM, MEM})); - for (i = 0; i < (MEM * 2) / getpagesize(); ++i) { - p = sys_mmap(0, getpagesize(), PROT_READ | PROT_WRITE, - MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE, -1, 0) - .addr; - if (p == MAP_FAILED) { - ASSERT_EQ(ENOMEM, errno); - _Exit(0); - } - rngset(p, getpagesize(), _rand64, -1); - } - _Exit(1); - } - EXPECT_TRUE(WIFEXITED(wstatus)); - EXPECT_FALSE(WIFSIGNALED(wstatus)); - EXPECT_EQ(0, WEXITSTATUS(wstatus)); - EXPECT_EQ(0, WTERMSIG(wstatus)); -} - -TEST(setrlimit, testPhysicalMemoryLimit) { - /* RLIMIT_RSS doesn't work on gnu/systemd */ - /* RLIMIT_RSS doesn't work on darwin */ - /* RLIMIT_RSS doesn't work on freebsd */ - /* RLIMIT_RSS doesn't work on netbsd */ - /* RLIMIT_RSS doesn't work on openbsd */ - /* of course it doesn't work on windows */ -} - -wontreturn void OnVfork(void *ctx) { - struct rlimit *rlim; - rlim = ctx; - rlim->rlim_cur -= 1; - ASSERT_EQ(0, getrlimit(RLIMIT_CPU, rlim)); - _Exit(0); -} - -TEST(setrlimit, isVforkSafe) { - int ws; - struct rlimit rlim[2]; - if (IsWindows()) - return; /* of course it doesn't work on windows */ - ASSERT_EQ(0, getrlimit(RLIMIT_CPU, rlim)); - ASSERT_NE(-1, (ws = xvspawn(OnVfork, rlim, 0))); - EXPECT_TRUE(WIFEXITED(ws)); - EXPECT_FALSE(WIFSIGNALED(ws)); - EXPECT_EQ(0, WEXITSTATUS(ws)); - EXPECT_EQ(0, WTERMSIG(ws)); - ASSERT_EQ(0, getrlimit(RLIMIT_CPU, rlim + 1)); - EXPECT_EQ(rlim[0].rlim_cur, rlim[1].rlim_cur); - EXPECT_EQ(rlim[0].rlim_max, rlim[1].rlim_max); -} - -#endif /* __x86_64__ */ diff --git a/test/libc/calls/shm_open_test.c b/test/libc/calls/shm_open_test.c index 3a83ea298..1d8f71a2b 100644 --- a/test/libc/calls/shm_open_test.c +++ b/test/libc/calls/shm_open_test.c @@ -9,6 +9,7 @@ #include "libc/dce.h" #include "libc/errno.h" #include "libc/runtime/runtime.h" +#include "libc/stdio/rand.h" #include "libc/stdio/stdio.h" #include "libc/str/str.h" #include "libc/sysv/consts/map.h" @@ -18,7 +19,6 @@ #include "libc/sysv/consts/sig.h" #include "libc/thread/semaphore.h" -#define SHM_PATH "/fc7261622dd420d8" #define STRING_SEND "hello" #define STRING_RECV "HELLO" @@ -29,13 +29,14 @@ struct shmbuf { char buf[256]; /* Data being transferred */ }; +char shm_path[64]; atomic_bool *ready; wontreturn void Bouncer(void) { /* Create shared memory object and set its size to the size of our structure. */ - int fd = shm_open(SHM_PATH, O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR); + int fd = shm_open(shm_path, O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR); if (fd == -1) { perror("shm_open(bouncer)"); exit(1); @@ -96,7 +97,7 @@ wontreturn void Sender(void) { /* Open the existing shared memory object and map it into the caller's address space. */ - int fd = shm_open(SHM_PATH, O_RDWR, 0); + int fd = shm_open(shm_path, O_RDWR, 0); if (fd == -1) { perror("shm_open(sender)"); exit(1); @@ -136,7 +137,7 @@ wontreturn void Sender(void) { /* Unlink the shared memory object. Even if the peer process is still using the object, this is okay. The object will be removed only after all open references are closed. */ - if (shm_unlink(SHM_PATH)) { + if (shm_unlink(shm_path)) { if (IsWindows() && errno == EACCES) { // TODO(jart): Make unlink() work better on Windows. } else { @@ -154,7 +155,7 @@ int pid2; void OnExit(void) { kill(pid1, SIGKILL); kill(pid2, SIGKILL); - shm_unlink(SHM_PATH); + shm_unlink(shm_path); } void OnTimeout(int sig) { @@ -164,6 +165,9 @@ void OnTimeout(int sig) { int main(int argc, char *argv[]) { + // create random shared memory name + sprintf(shm_path, "/shm_open_test-%ld", _rand64()); + // create synchronization object ready = _mapshared(1); diff --git a/test/libc/calls/sigaction_test.c b/test/libc/calls/sigaction_test.c index 9206016e2..b856b7b49 100644 --- a/test/libc/calls/sigaction_test.c +++ b/test/libc/calls/sigaction_test.c @@ -400,15 +400,16 @@ TEST(sigaction, ignoreSigSegv_notPossible) { _Exit(pSegfault(0)); TERMS(SIGSEGV); } +#endif +#if 0 +// TODO(jart): Use sigsuspend() to make not flaky. TEST(sigaction, killSigSegv_canBeIgnored) { int child, ws; - if (IsWindows()) return; // TODO sighandler_t old = signal(SIGSEGV, SIG_IGN); ASSERT_NE(-1, (child = fork())); - while (!child) { + while (!child) pause(); - } ASSERT_SYS(0, 0, kill(child, SIGSEGV)); EXPECT_SYS(0, 0, kill(child, SIGTERM)); EXPECT_SYS(0, child, wait(&ws)); diff --git a/test/libc/calls/sigaltstack_test.c b/test/libc/calls/sigaltstack_test.c index c2ed85c42..3c1d63bac 100644 --- a/test/libc/calls/sigaltstack_test.c +++ b/test/libc/calls/sigaltstack_test.c @@ -19,6 +19,9 @@ #include "libc/calls/struct/sigaltstack.h" #include "libc/calls/calls.h" #include "libc/errno.h" +#include "libc/mem/gc.h" +#include "libc/mem/mem.h" +#include "libc/runtime/sysconf.h" #include "libc/sysv/consts/ss.h" #include "libc/testlib/testlib.h" @@ -38,3 +41,13 @@ TEST(sigaltstack, disable) { EXPECT_SYS(0, 0, sigaltstack(0, &ss)); EXPECT_EQ(SS_DISABLE, ss.ss_flags); } + +TEST(sigaltstack, size_requirement) { + struct sigaltstack ss; + EXPECT_SYS(0, 0, sigaltstack(0, &ss)); + ss.ss_size = sysconf(_SC_MINSIGSTKSZ); + ss.ss_sp = gc(malloc(ss.ss_size)); + ss.ss_flags = 0; + ASSERT_SYS(0, 0, sigaltstack(&ss, 0)); + ASSERT_SYS(0, 0, sigaltstack(0, &ss)); +} diff --git a/test/libc/calls/signal_test.c b/test/libc/calls/signal_test.c deleted file mode 100644 index 74dfad41b..000000000 --- a/test/libc/calls/signal_test.c +++ /dev/null @@ -1,105 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ 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 │ -│ 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/sigaction.h" -#include "libc/calls/struct/sigset.h" -#include "libc/calls/ucontext.h" -#include "libc/dce.h" -#include "libc/log/check.h" -#include "libc/log/log.h" -#include "libc/runtime/runtime.h" -#include "libc/sysv/consts/sa.h" -#include "libc/sysv/consts/sig.h" -#include "libc/testlib/ezbench.h" -#include "libc/testlib/testlib.h" - -void OnUsr1(int sig) { - _exit(0); -} - -void SetUpOnce(void) { - sigset_t ss; - sigprocmask(SIG_SETMASK, 0, &ss); - ASSERT_SYS(0, 0, pledge("stdio proc", 0)); -} - -TEST(signal, test) { - ASSERT_NE(SIG_ERR, signal(SIGUSR1, OnUsr1)); - ASSERT_NE(-1, raise(SIGUSR1)); - __die(); -} - -//////////////////////////////////////////////////////////////////////////////// -// signal round-trip delivery takes about 1µs - -void OnSigTrap(int sig, siginfo_t *si, void *ctx) { -} - -void TrapBench(int n) { - for (int i = 0; i < n; ++i) { - DebugBreak(); - } -} - -BENCH(signal, trapBench) { - struct sigaction old; - struct sigaction sabus = {.sa_sigaction = OnSigTrap}; - ASSERT_SYS(0, 0, sigaction(SIGTRAP, &sabus, &old)); - EZBENCH_N("signal trap", 16, TrapBench(16)); - EZBENCH_N("signal trap", 256, TrapBench(256)); - EZBENCH_N("signal trap", 1024, TrapBench(1024)); - sigaction(SIGTRAP, &old, 0); -} - -BENCH(signal, trapBenchSiginfo) { - struct sigaction old; - struct sigaction sabus = {.sa_sigaction = OnSigTrap, .sa_flags = SA_SIGINFO}; - ASSERT_SYS(0, 0, sigaction(SIGTRAP, &sabus, &old)); - EZBENCH_N("siginfo trap", 16, TrapBench(16)); - EZBENCH_N("siginfo trap", 256, TrapBench(256)); - EZBENCH_N("siginfo trap", 1024, TrapBench(1024)); - sigaction(SIGTRAP, &old, 0); -} - -#ifdef __x86_64__ - -void OnSigHlt(int sig, siginfo_t *si, void *vctx) { - struct ucontext *ctx = vctx; - ctx->uc_mcontext.rip += 1; -} - -void HltBench(int n) { - for (int i = 0; i < n; ++i) { - asm("hlt"); - } -} - -BENCH(signal, hltBenchSiginfo) { - struct sigaction old[2]; - struct sigaction sabus = {.sa_sigaction = OnSigHlt, .sa_flags = SA_SIGINFO}; - ASSERT_SYS(0, 0, sigaction(SIGSEGV, &sabus, old + 0)); - ASSERT_SYS(0, 0, sigaction(SIGBUS, &sabus, old + 1)); - EZBENCH_N("siginfo hlt", 16, HltBench(16)); - EZBENCH_N("siginfo hlt", 256, HltBench(256)); - EZBENCH_N("siginfo hlt", 1024, HltBench(1024)); - sigaction(SIGSEGV, old + 0, 0); - sigaction(SIGBUS, old + 1, 0); -} - -#endif /* __x86_64__ */ diff --git a/test/libc/calls/sigsuspend_test.c b/test/libc/calls/sigsuspend_test.c index 457c3ba23..8386005ac 100644 --- a/test/libc/calls/sigsuspend_test.c +++ b/test/libc/calls/sigsuspend_test.c @@ -76,10 +76,6 @@ TEST(sigsuspend, testSignalQueuingSelf) { } TEST(sigsuspend, testSignalQueuingIpc) { - if (IsWindows()) { - // xxx: probably need a signal server to do this kind of signalling - return; - } int pid, ws; sigset_t neu, old, bits; struct sigaction oldusr1, oldusr2; diff --git a/test/libc/calls/stackoverflow1_test.c b/test/libc/calls/stackoverflow1_test.c index e2dfa79f2..c9397cbba 100644 --- a/test/libc/calls/stackoverflow1_test.c +++ b/test/libc/calls/stackoverflow1_test.c @@ -16,10 +16,14 @@ │ 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/struct/rlimit.h" #include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/sigaltstack.h" #include "libc/calls/struct/siginfo.h" +#include "libc/calls/struct/ucontext.internal.h" +#include "libc/calls/ucontext.h" #include "libc/dce.h" #include "libc/intrin/kprintf.h" #include "libc/limits.h" @@ -27,12 +31,15 @@ #include "libc/mem/mem.h" #include "libc/runtime/runtime.h" #include "libc/runtime/sysconf.h" +#include "libc/stdio/rand.h" +#include "libc/stdio/stdio.h" #include "libc/stdio/sysparam.h" +#include "libc/sysv/consts/map.h" +#include "libc/sysv/consts/prot.h" #include "libc/sysv/consts/rlimit.h" #include "libc/sysv/consts/sa.h" #include "libc/sysv/consts/sig.h" #include "libc/sysv/consts/ss.h" -#include "libc/testlib/testlib.h" #include "libc/thread/thread.h" /** @@ -42,15 +49,17 @@ */ jmp_buf recover; -volatile bool smashed_stack; +atomic_bool g_isdone; +atomic_bool smashed_stack; void CrashHandler(int sig, siginfo_t *si, void *ctx) { struct sigaltstack ss; - ASSERT_SYS(0, 0, sigaltstack(0, &ss)); - ASSERT_EQ(SS_ONSTACK, ss.ss_flags); - kprintf("kprintf avoids overflowing %G %p\n", si->si_signo, si->si_addr); + unassert(!sigaltstack(0, &ss)); + unassert(SS_ONSTACK == ss.ss_flags); + kprintf("kprintf avoids overflowing %G si_addr=%lx sp=%lx\n", si->si_signo, + si->si_addr, ((ucontext_t *)ctx)->uc_mcontext.SP); smashed_stack = true; - ASSERT_TRUE(__is_stack_overflow(si, ctx)); + // unassert(__is_stack_overflow(si, ctx)); // fuzzy with main thread longjmp(recover, 123); } @@ -63,7 +72,7 @@ void SetUp(void) { struct rlimit rl; getrlimit(RLIMIT_STACK, &rl); rl.rlim_cur = MIN(rl.rlim_cur, 2 * 1024 * 1024); - ASSERT_SYS(0, 0, setrlimit(RLIMIT_STACK, &rl)); + unassert(!setrlimit(RLIMIT_STACK, &rl)); } // set up the signal handler and alternative stack @@ -72,7 +81,7 @@ void SetUp(void) { ss.ss_flags = 0; ss.ss_size = sysconf(_SC_MINSIGSTKSZ) + 8192; ss.ss_sp = _mapanon(ss.ss_size); - ASSERT_SYS(0, 0, sigaltstack(&ss, 0)); + unassert(!sigaltstack(&ss, 0)); sa.sa_flags = SA_SIGINFO | SA_ONSTACK; // <-- important sigemptyset(&sa.sa_mask); sa.sa_sigaction = CrashHandler; @@ -89,20 +98,39 @@ int StackOverflow(int d) { return 0; } -TEST(stackoverflow, standardStack_altStack_process_longjmp) { +void *innocent_thread(void *arg) { + atomic_long dont_clobber_me_bro = 0; + while (!g_isdone) + unassert(!dont_clobber_me_bro); + return 0; +} + +int main() { + + // libc/intrin/stack.c is designed so that this thread's stack should + // be allocated right beneath the main thread's stack. our goal is to + // make sure overflowing the main stack won't clobber our poor thread + pthread_t th; + unassert(!pthread_create(&th, 0, innocent_thread, 0)); + + SetUp(); + int jumpcode; - if (!(jumpcode = setjmp(recover))) { - exit(StackOverflow(0)); - } - ASSERT_EQ(123, jumpcode); - ASSERT_TRUE(smashed_stack); + if (!(jumpcode = setjmp(recover))) + exit(StackOverflow(1)); + unassert(123 == jumpcode); + unassert(smashed_stack); + + // join the thread + g_isdone = true; + unassert(!pthread_join(th, 0)); // here's where longjmp() gets us into trouble struct sigaltstack ss; - ASSERT_SYS(0, 0, sigaltstack(0, &ss)); + unassert(!sigaltstack(0, &ss)); if (IsXnu() || IsNetbsd()) { - ASSERT_EQ(SS_ONSTACK, ss.ss_flags); // wut + unassert(SS_ONSTACK == ss.ss_flags); // wut } else { - ASSERT_EQ(0, ss.ss_flags); + unassert(0 == ss.ss_flags); } } diff --git a/test/libc/calls/stackoverflow2_test.c b/test/libc/calls/stackoverflow2_test.c index 0afa6b695..520d952e8 100644 --- a/test/libc/calls/stackoverflow2_test.c +++ b/test/libc/calls/stackoverflow2_test.c @@ -16,20 +16,26 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" #include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/sigaltstack.h" #include "libc/calls/struct/siginfo.h" +#include "libc/calls/struct/ucontext.internal.h" +#include "libc/calls/ucontext.h" +#include "libc/cosmo.h" #include "libc/dce.h" #include "libc/intrin/kprintf.h" +#include "libc/intrin/maps.h" #include "libc/limits.h" #include "libc/mem/gc.h" #include "libc/mem/mem.h" #include "libc/runtime/runtime.h" +#include "libc/runtime/stack.h" #include "libc/runtime/sysconf.h" +#include "libc/stdio/stdio.h" #include "libc/sysv/consts/sa.h" #include "libc/sysv/consts/sig.h" #include "libc/sysv/consts/ss.h" -#include "libc/testlib/testlib.h" #include "libc/thread/thread.h" /** @@ -38,17 +44,20 @@ * simple but it can upset kernels / libraries */ -jmp_buf recover; -volatile bool smashed_stack; +sigjmp_buf recover; +atomic_bool is_done; +atomic_bool smashed_stack; +atomic_bool clobbered_other_thread; void CrashHandler(int sig, siginfo_t *si, void *ctx) { struct sigaltstack ss; - ASSERT_SYS(0, 0, sigaltstack(0, &ss)); - ASSERT_EQ(SS_ONSTACK, ss.ss_flags); - kprintf("kprintf avoids overflowing %G %p\n", si->si_signo, si->si_addr); + unassert(!sigaltstack(0, &ss)); + unassert(SS_ONSTACK == ss.ss_flags); + kprintf("kprintf avoids overflowing %G si_addr=%lx sp=%lx\n", si->si_signo, + si->si_addr, ((ucontext_t *)ctx)->uc_mcontext.SP); smashed_stack = true; - ASSERT_TRUE(__is_stack_overflow(si, ctx)); - longjmp(recover, 123); + unassert(__is_stack_overflow(si, ctx)); + siglongjmp(recover, 123); } int StackOverflow(int d) { @@ -65,40 +74,51 @@ void *MyPosixThread(void *arg) { struct sigaction sa, o1, o2; struct sigaltstack ss; ss.ss_flags = 0; - ss.ss_size = sysconf(_SC_MINSIGSTKSZ) + 4096; + ss.ss_size = sysconf(_SC_MINSIGSTKSZ) + 2048; ss.ss_sp = gc(malloc(ss.ss_size)); - ASSERT_SYS(0, 0, sigaltstack(&ss, 0)); + unassert(!sigaltstack(&ss, 0)); sa.sa_flags = SA_SIGINFO | SA_ONSTACK; // <-- important sigemptyset(&sa.sa_mask); sa.sa_sigaction = CrashHandler; sigaction(SIGBUS, &sa, &o1); sigaction(SIGSEGV, &sa, &o2); - if (!(jumpcode = setjmp(recover))) { - exit(StackOverflow(0)); - } - ASSERT_EQ(123, jumpcode); + if (!(jumpcode = sigsetjmp(recover, 1))) + exit(StackOverflow(1)); + unassert(123 == jumpcode); sigaction(SIGSEGV, &o2, 0); sigaction(SIGBUS, &o1, 0); // here's where longjmp() gets us into trouble - ASSERT_SYS(0, 0, sigaltstack(0, &ss)); + unassert(!sigaltstack(0, &ss)); if (IsXnu() || IsNetbsd()) { - ASSERT_EQ(SS_ONSTACK, ss.ss_flags); // wut + unassert(SS_ONSTACK == ss.ss_flags); // wut } else { - ASSERT_EQ(0, ss.ss_flags); + unassert(!ss.ss_flags); } return 0; } -TEST(stackoverflow, standardStack_altStack_thread_longjmp) { - pthread_t th; +void *InnocentThread(void *arg) { + atomic_long dont_clobber_me_bro = 0; + while (!is_done) + if (dont_clobber_me_bro) + clobbered_other_thread = true; + pthread_exit(0); +} + +int main() { + pthread_t th, in; struct sigaltstack ss; for (int i = 0; i < 2; ++i) { + is_done = false; smashed_stack = false; - pthread_create(&th, 0, MyPosixThread, 0); - pthread_join(th, 0); - ASSERT_TRUE(smashed_stack); - // this should be SS_DISABLE but ShowCrashReports() creates an alt stack - ASSERT_SYS(0, 0, sigaltstack(0, &ss)); - ASSERT_EQ(0, ss.ss_flags); + unassert(!pthread_create(&th, 0, MyPosixThread, 0)); + unassert(!pthread_create(&in, 0, InnocentThread, 0)); + unassert(!pthread_join(th, 0)); + unassert(smashed_stack); + unassert(!sigaltstack(0, &ss)); + unassert(ss.ss_flags == SS_DISABLE); + unassert(!clobbered_other_thread); + is_done = true; + unassert(!pthread_join(in, 0)); } } diff --git a/test/libc/calls/stackoverflow3_test.c b/test/libc/calls/stackoverflow3_test.c index 81ff8c1f9..b83ebcf25 100644 --- a/test/libc/calls/stackoverflow3_test.c +++ b/test/libc/calls/stackoverflow3_test.c @@ -98,7 +98,7 @@ void *MyPosixThread(void *arg) { struct sigaction sa; struct sigaltstack ss; ss.ss_flags = 0; - ss.ss_size = sysconf(_SC_MINSIGSTKSZ) + 4096; + ss.ss_size = sysconf(_SC_MINSIGSTKSZ) + 8192; ss.ss_sp = gc(malloc(ss.ss_size)); ASSERT_SYS(0, 0, sigaltstack(&ss, 0)); sa.sa_flags = SA_SIGINFO | SA_ONSTACK; // <-- important @@ -106,7 +106,7 @@ void *MyPosixThread(void *arg) { sa.sa_sigaction = CrashHandler; sigaction(SIGBUS, &sa, 0); sigaction(SIGSEGV, &sa, 0); - exit(StackOverflow(0)); + exit(StackOverflow(1)); return 0; } diff --git a/test/libc/calls/stackoverflow4_test.c b/test/libc/calls/stackoverflow4_test.c index 8266ddda0..a9b1eab2f 100644 --- a/test/libc/calls/stackoverflow4_test.c +++ b/test/libc/calls/stackoverflow4_test.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/struct/sigaction.h" #include "libc/calls/struct/sigaltstack.h" #include "libc/calls/struct/siginfo.h" @@ -40,8 +41,9 @@ volatile bool smashed_stack; -void CrashHandler(int sig) { +void CrashHandler(int sig, siginfo_t *si, void *ctx) { smashed_stack = true; + unassert(__is_stack_overflow(si, ctx)); pthread_exit((void *)123L); } @@ -58,15 +60,15 @@ void *MyPosixThread(void *arg) { struct sigaction sa; struct sigaltstack ss; ss.ss_flags = 0; - ss.ss_size = sysconf(_SC_MINSIGSTKSZ) + 4096; + ss.ss_size = sysconf(_SC_MINSIGSTKSZ) + 1024; ss.ss_sp = gc(malloc(ss.ss_size)); ASSERT_SYS(0, 0, sigaltstack(&ss, 0)); sa.sa_flags = SA_SIGINFO | SA_ONSTACK; // <-- important sigemptyset(&sa.sa_mask); - sa.sa_handler = CrashHandler; + sa.sa_sigaction = CrashHandler; sigaction(SIGBUS, &sa, 0); sigaction(SIGSEGV, &sa, 0); - exit(StackOverflow(0)); + exit(StackOverflow(1)); return 0; } diff --git a/test/libc/calls/stackoverflow5_test.c b/test/libc/calls/stackoverflow5_test.c index 7a3398045..29a4097d1 100644 --- a/test/libc/calls/stackoverflow5_test.c +++ b/test/libc/calls/stackoverflow5_test.c @@ -16,22 +16,28 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include -#include -#include -#include -#include +#include "libc/assert.h" +#include "libc/calls/calls.h" +#include "libc/calls/struct/sigaction.h" +#include "libc/calls/struct/siginfo.h" +#include "libc/runtime/runtime.h" +#include "libc/sysv/consts/sa.h" +#include "libc/sysv/consts/sig.h" +#include "libc/sysv/consts/ss.h" +#include "libc/thread/thread.h" +#include "libc/thread/tls.h" /** - * stack overflow recovery technique #5 - * use the cosmo posix threads extensions + * stack overflow test #5 + * - make sure fork() preserves sigaltstack() + * - make sure fork() preserves guard page status */ -sig_atomic_t smashed_stack; +jmp_buf recover; -void CrashHandler(int sig) { - smashed_stack = true; - pthread_exit(0); +void CrashHandler(int sig, siginfo_t *si, void *ctx) { + unassert(__is_stack_overflow(si, ctx)); + longjmp(recover, 123); } int StackOverflow(int d) { @@ -44,42 +50,40 @@ int StackOverflow(int d) { } void *MyPosixThread(void *arg) { - exit(StackOverflow(0)); + int pid; + unassert(__get_tls()->tib_sigstack_addr); + unassert((pid = fork()) != -1); + if (!pid) { + int jumpcode; + if (!(jumpcode = setjmp(recover))) { + StackOverflow(1); + _Exit(1); + } + unassert(123 == jumpcode); + } else { + int ws; + unassert(wait(&ws) != -1); + unassert(!ws); + pthread_exit(0); + } return 0; } int main() { - // choose the most dangerously small size possible - size_t sigstacksize = sysconf(_SC_MINSIGSTKSZ) + 2048; - - // setup signal handler struct sigaction sa; + sa.sa_flags = SA_SIGINFO | SA_ONSTACK; sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_ONSTACK; - sa.sa_handler = CrashHandler; - if (sigaction(SIGBUS, &sa, 0)) - return 1; - if (sigaction(SIGSEGV, &sa, 0)) - return 2; + sa.sa_sigaction = CrashHandler; + unassert(!sigaction(SIGBUS, &sa, 0)); + unassert(!sigaction(SIGSEGV, &sa, 0)); - // create thread with signal stack - pthread_t id; + pthread_t th; pthread_attr_t attr; - if (pthread_attr_init(&attr)) - return 3; - if (pthread_attr_setguardsize(&attr, getpagesize())) - return 4; - if (pthread_attr_setsigaltstacksize_np(&attr, sigstacksize)) - return 5; - if (pthread_create(&id, &attr, MyPosixThread, 0)) - return 6; - if (pthread_attr_destroy(&attr)) - return 7; - if (pthread_join(id, 0)) - return 8; - if (!smashed_stack) - return 9; - - CheckForMemoryLeaks(); + unassert(!pthread_attr_init(&attr)); + unassert(!pthread_attr_setguardsize(&attr, getpagesize())); + unassert(!pthread_attr_setsigaltstacksize_np(&attr, SIGSTKSZ)); + unassert(!pthread_create(&th, &attr, MyPosixThread, 0)); + unassert(!pthread_attr_destroy(&attr)); + unassert(!pthread_join(th, 0)); } diff --git a/test/libc/intrin/BUILD.mk b/test/libc/intrin/BUILD.mk index 1072638fe..7a1ce1756 100644 --- a/test/libc/intrin/BUILD.mk +++ b/test/libc/intrin/BUILD.mk @@ -59,6 +59,15 @@ o/$(MODE)/test/libc/intrin/%.dbg: \ $(TEST_LIBC_INTRIN_DEPS) \ o/$(MODE)/test/libc/intrin/%.o \ o/$(MODE)/test/libc/intrin/intrin.pkg \ + $(LIBC_TESTMAIN) \ + $(CRT) \ + $(APE_NO_MODIFY_SELF) + @$(APELINK) + +o/$(MODE)/test/libc/intrin/mmap_test.dbg: \ + $(TEST_LIBC_INTRIN_DEPS) \ + o/$(MODE)/test/libc/intrin/mmap_test.o \ + o/$(MODE)/test/libc/intrin/intrin.pkg \ o/$(MODE)/test/libc/mem/prog/life.elf.zip.o \ $(LIBC_TESTMAIN) \ $(CRT) \ diff --git a/test/libc/intrin/lock_test.c b/test/libc/intrin/lock_test.c index 06782deed..b73a94f85 100644 --- a/test/libc/intrin/lock_test.c +++ b/test/libc/intrin/lock_test.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/atomic.h" #include "libc/calls/calls.h" +#include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/timespec.h" #include "libc/errno.h" #include "libc/fmt/itoa.h" @@ -28,8 +29,10 @@ #include "libc/runtime/internal.h" #include "libc/runtime/runtime.h" #include "libc/runtime/stack.h" +#include "libc/runtime/symbols.internal.h" #include "libc/str/str.h" #include "libc/sysv/consts/clone.h" +#include "libc/sysv/consts/sig.h" #include "libc/thread/thread.h" #include "libc/thread/tls.h" #include "third_party/nsync/mu.h" @@ -62,6 +65,9 @@ pthread_mutex_t mu; __assert_eq_fail(__FILE__, __LINE__, #WANT, #GOT, _want, _got); \ } while (0) +void ignore_signal(int sig) { +} + void __assert_eq_fail(const char *file, int line, const char *wantstr, const char *gotstr, long want, long got) { kprintf("%s:%d: %s vs. %s was %ld vs. %ld (%s)\n", file, line, wantstr, @@ -112,10 +118,15 @@ void TestContendedLock(const char *name, int kind) { char *stk; double ns; errno_t rc; + int x, i, n = 10000; struct timespec t1, t2; pthread_mutexattr_t attr; - int tid, x, i, n = 10000; - struct CosmoTib tib = {.tib_self = &tib, .tib_self2 = &tib, .tib_tid = -1}; + struct CosmoTib tib = { + .tib_self = &tib, + .tib_self2 = &tib, + .tib_ctid = -1, + .tib_ptid = 0, + }; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, kind); pthread_mutex_init(&mu, &attr); @@ -127,7 +138,7 @@ void TestContendedLock(const char *name, int kind) { CLONE_VM | CLONE_THREAD | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_SYSVSEM | CLONE_PARENT_SETTID | CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | CLONE_SETTLS, - 0, &tid, &tib, &tib.tib_tid); + 0, &tib.tib_ptid, &tib, &tib.tib_ctid); if (rc) { kprintf("clone failed: %s\n", strerror(rc)); _Exit(1); @@ -143,7 +154,7 @@ void TestContendedLock(const char *name, int kind) { ASSERT_EQ(0, pthread_mutex_unlock(&mu)); } t2 = timespec_real(); - while (tib.tib_tid) + while (tib.tib_ctid) donothing; ASSERT_EQ(1, atomic_load(&success)); ASSERT_EQ(0, atomic_load(&counter)); @@ -177,6 +188,12 @@ void TestUncontendedLock(const char *name, int kind) { int main(int argc, char *argv[]) { pthread_mutexattr_t attr; +#ifdef MODE_DBG + GetSymbolTable(); + signal(SIGTRAP, ignore_signal); + kprintf("running %s\n", argv[0]); +#endif + #ifdef __aarch64__ // our usage of raw clone() is probably broken in aarch64 // we should just get rid of clone() @@ -190,7 +207,7 @@ int main(int argc, char *argv[]) { } ASSERT_EQ(0, pthread_mutexattr_init(&attr)); - ASSERT_EQ(0, pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL)); + ASSERT_EQ(0, pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT)); ASSERT_EQ(0, pthread_mutex_init(&mu, &attr)); ASSERT_EQ(0, pthread_mutexattr_destroy(&attr)); ASSERT_EQ(0, pthread_mutex_lock(&mu)); @@ -216,28 +233,12 @@ int main(int argc, char *argv[]) { ASSERT_EQ(0, pthread_mutex_unlock(&mu)); ASSERT_EQ(0, pthread_mutex_destroy(&mu)); - ASSERT_EQ(1, __tls_enabled); - - TestUncontendedLock("PTHREAD_MUTEX_NORMAL RAW TLS", PTHREAD_MUTEX_NORMAL); + TestUncontendedLock("PTHREAD_MUTEX_DEFAULT RAW TLS", PTHREAD_MUTEX_DEFAULT); TestUncontendedLock("PTHREAD_MUTEX_RECURSIVE RAW TLS", PTHREAD_MUTEX_RECURSIVE); - TestUncontendedLock("PTHREAD_MUTEX_ERRORCHECK RAW TLS", - PTHREAD_MUTEX_ERRORCHECK); - TestContendedLock("PTHREAD_MUTEX_NORMAL RAW TLS", PTHREAD_MUTEX_NORMAL); + TestContendedLock("PTHREAD_MUTEX_DEFAULT RAW TLS", PTHREAD_MUTEX_DEFAULT); TestContendedLock("PTHREAD_MUTEX_RECURSIVE RAW TLS", PTHREAD_MUTEX_RECURSIVE); - TestContendedLock("PTHREAD_MUTEX_ERRORCHECK RAW TLS", - PTHREAD_MUTEX_ERRORCHECK); - - __tls_enabled_set(false); - - TestUncontendedLock("PTHREAD_MUTEX_NORMAL RAW", PTHREAD_MUTEX_NORMAL); - TestUncontendedLock("PTHREAD_MUTEX_RECURSIVE RAW", PTHREAD_MUTEX_RECURSIVE); - TestUncontendedLock("PTHREAD_MUTEX_ERRORCHECK RAW", PTHREAD_MUTEX_ERRORCHECK); - - TestContendedLock("PTHREAD_MUTEX_NORMAL RAW", PTHREAD_MUTEX_NORMAL); - TestContendedLock("PTHREAD_MUTEX_RECURSIVE RAW", PTHREAD_MUTEX_RECURSIVE); - TestContendedLock("PTHREAD_MUTEX_ERRORCHECK RAW", PTHREAD_MUTEX_ERRORCHECK); // } diff --git a/test/libc/intrin/lockipc_test.c b/test/libc/intrin/lockipc_test.c index 0f3467bc2..30878c699 100644 --- a/test/libc/intrin/lockipc_test.c +++ b/test/libc/intrin/lockipc_test.c @@ -52,7 +52,7 @@ TEST(lockipc, mutex) { // create shared mutex pthread_mutexattr_t mattr; pthread_mutexattr_init(&mattr); - pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_NORMAL); + pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_DEFAULT); pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED); pthread_mutex_init(&shm->mutex, &mattr); pthread_mutexattr_destroy(&mattr); diff --git a/test/libc/intrin/memset_test.c b/test/libc/intrin/memset_test.c index f935e8bfa..cd05645e9 100644 --- a/test/libc/intrin/memset_test.c +++ b/test/libc/intrin/memset_test.c @@ -66,9 +66,11 @@ TEST(bzero, hug) { #define N (256 * 1024 * 1024) -BENCH(strlen, bench) { +BENCH(memset, bench) { + void *memset_(void *, int, size_t) asm("memset"); + printf("\n"); static char A[N]; memset(A, 2, N); for (int n = 1; n <= N; n *= 2) - BENCHMARK(100, n, X(memset(V(A), 1, n))); + BENCHMARK(100, n, X(memset_(V(A), 0, n))); } diff --git a/test/libc/intrin/mmap_test.c b/test/libc/intrin/mmap_test.c index 9475ccacc..e44ee223e 100644 --- a/test/libc/intrin/mmap_test.c +++ b/test/libc/intrin/mmap_test.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "ape/sections.internal.h" +#include "libc/assert.h" #include "libc/calls/calls.h" #include "libc/dce.h" #include "libc/errno.h" @@ -27,12 +28,14 @@ #include "libc/runtime/sysconf.h" #include "libc/stdio/rand.h" #include "libc/stdio/stdio.h" +#include "libc/stdio/sysparam.h" #include "libc/str/str.h" #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/msync.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/prot.h" #include "libc/testlib/benchmark.h" +#include "libc/testlib/subprocess.h" #include "libc/testlib/testlib.h" #include "libc/x/xspawn.h" @@ -56,6 +59,10 @@ void SetUpOnce(void) { // ASSERT_SYS(0, 0, pledge("stdio rpath wpath cpath proc", 0)); } +void TearDown(void) { + ASSERT_FALSE(__maps_held()); +} + TEST(mmap, zeroSize) { ASSERT_SYS(EINVAL, MAP_FAILED, mmap(NULL, 0, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)); @@ -95,7 +102,7 @@ TEST(mmap, pageBeyondGone) { MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); ASSERT_NE(MAP_FAILED, p); EXPECT_TRUE(testlib_memoryexists(p)); - EXPECT_FALSE(testlib_memoryexists(p + 1)); // b/c kisdangerous + EXPECT_TRUE(testlib_memoryexists(p + 1)); EXPECT_FALSE(testlib_memoryexists(p + pagesz)); ASSERT_EQ(0, munmap(p, 1)); } @@ -109,6 +116,42 @@ TEST(mmap, fixedTaken) { EXPECT_SYS(0, 0, munmap(p, 1)); } +TEST(mmap, anon_rw_to_rx) { + char *p; + ASSERT_NE(MAP_FAILED, (p = mmap(0, 1, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0))); + ASSERT_SYS(0, 0, mprotect(p, 1, PROT_READ | PROT_EXEC)); + ASSERT_SYS(0, 0, munmap(p, 1)); +} + +TEST(mmap, anon_rw_fork_to_rx) { + char *p; + ASSERT_NE(MAP_FAILED, (p = mmap(0, 1, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0))); + SPAWN(fork); + ASSERT_SYS(0, 0, mprotect(p, 1, PROT_READ | PROT_EXEC)); + EXITS(0); + ASSERT_SYS(0, 0, munmap(p, 1)); +} + +TEST(mmap, anon_r_to_rw) { + char *p; + ASSERT_NE(MAP_FAILED, + (p = mmap(0, 1, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0))); + ASSERT_SYS(0, 0, mprotect(p, 1, PROT_READ | PROT_WRITE)); + ASSERT_SYS(0, 0, munmap(p, 1)); +} + +TEST(mmap, anon_r_fork_to_rw) { + char *p; + ASSERT_NE(MAP_FAILED, + (p = mmap(0, 1, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0))); + SPAWN(fork); + ASSERT_SYS(0, 0, mprotect(p, 1, PROT_READ | PROT_WRITE)); + EXITS(0); + ASSERT_SYS(0, 0, munmap(p, 1)); +} + TEST(mmap, hint) { char *p; @@ -184,7 +227,7 @@ TEST(mmap, smallerThanPage_mapsRemainder) { ASSERT_NE(MAP_FAILED, map); EXPECT_TRUE(testlib_memoryexists(map)); EXPECT_TRUE(testlib_pokememory(map + (pagesz - 1))); - EXPECT_TRUE(!testlib_memoryexists(map + (pagesz - 1))); + EXPECT_TRUE(testlib_memoryexists(map + (pagesz - 1))); EXPECT_SYS(0, 0, munmap(map, 1)); EXPECT_FALSE(testlib_memoryexists(map)); EXPECT_FALSE(testlib_memoryexists(map + (pagesz - 1))); @@ -331,6 +374,172 @@ TEST(mmap, pml5t) { } } +TEST(mmap, windows) { + if (!IsWindows()) + return; + int count = __maps.count; + char *base = __maps_randaddr(); + + ASSERT_EQ(base, mmap(base, pagesz * 3, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + ASSERT_EQ((count += 1), __maps.count); + + // isn't granularity aligned + ASSERT_SYS(EINVAL, -1, munmap(base + pagesz, pagesz)); + + // doesn't overlap any maps + ASSERT_SYS(0, 0, munmap(base + gransz, pagesz)); + ASSERT_EQ(count, __maps.count); + + // doesn't overlap any maps + ASSERT_SYS(0, 0, munmap(base - gransz, gransz)); + ASSERT_EQ(count, __maps.count); + + // partially overlaps map + ASSERT_SYS(ENOTSUP, -1, munmap(base, pagesz)); + ASSERT_EQ(count, __maps.count); + + // envelops map + ASSERT_SYS(0, 0, munmap(base - gransz, gransz + pagesz * 4)); + ASSERT_EQ((count -= 1), __maps.count); + + // win32 actually unmapped map + ASSERT_EQ(base, mmap(base, pagesz * 3, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + ASSERT_EQ((count += 1), __maps.count); + + // change status of middle page results in three fragments + ASSERT_SYS(0, 0, mprotect(base + pagesz, pagesz, PROT_NONE)); + ASSERT_EQ((count += 2), __maps.count); + + // change status back (todo: should reunite fragments) + ASSERT_SYS(0, 0, mprotect(base + pagesz, pagesz, PROT_READ | PROT_WRITE)); + ASSERT_EQ(count, __maps.count); + + // clean up + ASSERT_SYS(0, 0, munmap(base, pagesz * 3)); + ASSERT_EQ((count -= 3), __maps.count); +} + +TEST(mmap, windows_partial_overlap_enotsup) { + if (!IsWindows()) + return; + int count = __maps.count; + char *base = __maps_randaddr(); + + ASSERT_EQ(base, mmap(base, gransz * 3, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + ASSERT_EQ((count += 1), __maps.count); + + // partially overlaps on left + ASSERT_SYS(ENOTSUP, -1, munmap(base - gransz, gransz * 2)); + ASSERT_SYS(ENOTSUP, -1, munmap(base, gransz * 2)); + ASSERT_EQ(count, __maps.count); + + // partially overlaps the middle + ASSERT_SYS(ENOTSUP, -1, munmap(base + gransz * 1, gransz)); + ASSERT_SYS(ENOTSUP, -1, munmap(base + gransz * 1, gransz * 2)); + ASSERT_EQ(count, __maps.count); + + // partially overlaps on right + ASSERT_SYS(ENOTSUP, -1, munmap(base + gransz * 2, gransz * 2)); + ASSERT_EQ(count, __maps.count); + + // doesn't overlap any maps + ASSERT_SYS(0, 0, munmap(base - gransz, gransz)); + ASSERT_SYS(0, 0, munmap(base + gransz * 3, gransz)); + ASSERT_EQ(count, __maps.count); + + // unmap envelops + ASSERT_SYS(0, 0, munmap(base - gransz, gransz * 4)); + ASSERT_EQ((count -= 1), __maps.count); + + // win32 actually removed the memory + ASSERT_EQ(base, mmap(base, gransz * 3, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + ASSERT_EQ((count += 1), __maps.count); + + // clean up + ASSERT_SYS(0, 0, munmap(base, gransz * 3)); + ASSERT_EQ((count -= 1), __maps.count); +} + +TEST(munmap, windows_not_all_fragments_included_enotsup) { + if (!IsWindows()) + return; + int count = __maps.count; + char *base = __maps_randaddr(); + + ASSERT_EQ(base, mmap(base, gransz * 3, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + ASSERT_EQ((count += 1), __maps.count); + + // win32 memory actually exists + ASSERT_SYS(EEXIST, MAP_FAILED, + mmap(base, gransz * 3, PROT_READ | PROT_WRITE, + MAP_FIXED_NOREPLACE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + ASSERT_SYS(EEXIST, MAP_FAILED, + mmap(base + gransz * 0, gransz, PROT_READ | PROT_WRITE, + MAP_FIXED_NOREPLACE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + ASSERT_SYS(EEXIST, MAP_FAILED, + mmap(base + gransz * 1, gransz, PROT_READ | PROT_WRITE, + MAP_FIXED_NOREPLACE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + ASSERT_SYS(EEXIST, MAP_FAILED, + mmap(base + gransz * 2, gransz, PROT_READ | PROT_WRITE, + MAP_FIXED_NOREPLACE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + + // change status of middle page results in three fragments + ASSERT_SYS(0, 0, mprotect(base + gransz, gransz, PROT_NONE)); + ASSERT_EQ((count += 2), __maps.count); + + // partially overlaps on left + ASSERT_SYS(ENOTSUP, -1, munmap(base - gransz, gransz * 2)); + ASSERT_SYS(ENOTSUP, -1, munmap(base, gransz * 2)); + ASSERT_EQ(count, __maps.count); + + // partially overlaps the middle + ASSERT_SYS(ENOTSUP, -1, munmap(base + gransz * 1, gransz)); + ASSERT_SYS(ENOTSUP, -1, munmap(base + gransz * 1, gransz * 2)); + ASSERT_EQ(count, __maps.count); + + // partially overlaps on right + ASSERT_SYS(ENOTSUP, -1, munmap(base + gransz * 2, gransz * 2)); + ASSERT_EQ(count, __maps.count); + + // doesn't overlap any maps + ASSERT_SYS(0, 0, munmap(base - gransz, gransz)); + ASSERT_SYS(0, 0, munmap(base + gransz * 3, gransz)); + ASSERT_EQ(count, __maps.count); + + // unmap envelops + ASSERT_SYS(0, 0, munmap(base - gransz, gransz * 4)); + ASSERT_EQ((count -= 3), __maps.count); + + // win32 actually removed the memory + ASSERT_EQ(base, mmap(base, gransz * 3, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + ASSERT_EQ((count += 1), __maps.count); + + // clean up + ASSERT_SYS(0, 0, munmap(base, gransz * 3)); + ASSERT_EQ((count -= 1), __maps.count); +} + +TEST(mmap, windows_private_memory_fork_uses_virtualfree) { + if (IsFreebsd()) + return; // freebsd can't take a hint + char *base; + ASSERT_NE(MAP_FAILED, (base = mmap(0, gransz * 3, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0))); + SPAWN(fork); + ASSERT_SYS(0, 0, munmap(base, gransz * 3)); + ASSERT_EQ(base, mmap(base, gransz * 3, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + ASSERT_SYS(0, 0, munmap(base, gransz * 3)); + EXITS(0); + ASSERT_SYS(0, 0, munmap(base, gransz * 3)); +} + //////////////////////////////////////////////////////////////////////////////// // zipos NON-SHARED READ-ONLY FILE MEMORY @@ -534,35 +743,31 @@ void BenchMmapPrivate(void) { void *p; p = mmap(0, (sizes[count] = rand() % (pagesz * 500)), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - if (p == MAP_FAILED) - __builtin_trap(); + ASSERT_NE(MAP_FAILED, p); ptrs[count] = p; ++count; } void BenchUnmap(void) { --count; - if (munmap(ptrs[count], sizes[count])) - __builtin_trap(); + ASSERT_SYS(0, 0, munmap(ptrs[count], sizes[count])); } void BenchBigMmap(void) { void *p; p = mmap(0, 101 * 1024 * 1024, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - if (p == MAP_FAILED) - __builtin_trap(); + ASSERT_NE(MAP_FAILED, p); ptrs[count++] = p; } void BenchBigMunmap(void) { - if (munmap(ptrs[--count], 101 * 1024 * 1024)) - __builtin_trap(); + ASSERT_SYS(0, 0, munmap(ptrs[--count], 101 * 1024 * 1024)); } TEST(mmap, bench) { BENCHMARK(N, 1, BenchMmapPrivate()); BENCHMARK(N, 1, BenchUnmap()); - // BENCHMARK(N, 1, BenchBigMmap()); - // BENCHMARK(N, 1, BenchBigMunmap()); + /* BENCHMARK(N, 1, BenchBigMmap()); */ + /* BENCHMARK(N, 1, BenchBigMunmap()); */ } diff --git a/test/libc/intrin/mprotect_test.c b/test/libc/intrin/mprotect_test.c index 4c2a6a5c3..a04af31c2 100644 --- a/test/libc/intrin/mprotect_test.c +++ b/test/libc/intrin/mprotect_test.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/struct/sigaction.h" #include "libc/calls/ucontext.h" @@ -108,15 +109,15 @@ void SetUp(void) { .sa_flags = SA_SIGINFO | SA_RESETHAND}; struct sigaction sasegv = {.sa_sigaction = OnSigSegv, .sa_flags = SA_SIGINFO | SA_RESETHAND}; - sigaction(SIGBUS, &sabus, old + 0); - sigaction(SIGSEGV, &sasegv, old + 1); + unassert(!sigaction(SIGBUS, &sabus, old + 0)); + unassert(!sigaction(SIGSEGV, &sasegv, old + 1)); gotbusted = false; gotsegv = false; } void TearDown(void) { - sigaction(SIGBUS, old + 0, 0); - sigaction(SIGSEGV, old + 1, 0); + unassert(!sigaction(SIGBUS, old + 0, 0)); + unassert(!sigaction(SIGSEGV, old + 1, 0)); } TEST(mprotect, testOkMemory) { diff --git a/test/libc/intrin/munmap_test.c b/test/libc/intrin/munmap_test.c index eb61bb282..630325687 100644 --- a/test/libc/intrin/munmap_test.c +++ b/test/libc/intrin/munmap_test.c @@ -53,26 +53,106 @@ TEST(munmap, test) { EXPECT_FALSE(testlib_memoryexists(p)); } +TEST(munmap, carveMemory) { + if (IsWindows()) + return; // needs carving + char *p; + int count = __maps.count; + ASSERT_NE(MAP_FAILED, + (p = mmap(__maps_randaddr(), gransz * 3, PROT_READ | PROT_WRITE, + MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0))); + EXPECT_EQ(count + 1, __maps.count); + count = __maps.count; + EXPECT_TRUE(testlib_memoryexists(p + gransz * 0)); + EXPECT_TRUE(testlib_memoryexists(p + gransz * 1)); + EXPECT_TRUE(testlib_memoryexists(p + gransz * 2)); + EXPECT_SYS(0, 0, munmap(p + gransz * 0, gransz)); + EXPECT_EQ(count + 0, __maps.count); + count = __maps.count; + EXPECT_FALSE(testlib_memoryexists(p + gransz * 0)); + EXPECT_TRUE(testlib_memoryexists(p + gransz * 1)); + EXPECT_TRUE(testlib_memoryexists(p + gransz * 2)); + EXPECT_SYS(0, 0, munmap(p + gransz * 2, gransz)); + EXPECT_EQ(count + 0, __maps.count); + count = __maps.count; + EXPECT_FALSE(testlib_memoryexists(p + gransz * 0)); + EXPECT_TRUE(testlib_memoryexists(p + gransz * 1)); + EXPECT_FALSE(testlib_memoryexists(p + gransz * 2)); + EXPECT_SYS(0, 0, munmap(p + gransz * 1, gransz)); + EXPECT_EQ(count - 1, __maps.count); + count = __maps.count; + EXPECT_FALSE(testlib_memoryexists(p + gransz * 0)); + EXPECT_FALSE(testlib_memoryexists(p + gransz * 1)); + EXPECT_FALSE(testlib_memoryexists(p + gransz * 2)); +} + TEST(munmap, punchHoleInMemory) { if (IsWindows()) return; // needs carving char *p; - ASSERT_NE(MAP_FAILED, (p = mmap(0, gransz * 3, PROT_READ | PROT_WRITE, - MAP_ANONYMOUS | MAP_PRIVATE, -1, 0))); + int count = __maps.count; + ASSERT_NE(MAP_FAILED, + (p = mmap(__maps_randaddr(), gransz * 3, PROT_READ | PROT_WRITE, + MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0))); + EXPECT_EQ(count + 1, __maps.count); + count = __maps.count; EXPECT_TRUE(testlib_memoryexists(p + gransz * 0)); EXPECT_TRUE(testlib_memoryexists(p + gransz * 1)); EXPECT_TRUE(testlib_memoryexists(p + gransz * 2)); EXPECT_SYS(0, 0, munmap(p + gransz, gransz)); + EXPECT_EQ(count + 1, __maps.count); + count = __maps.count; EXPECT_TRUE(testlib_memoryexists(p + gransz * 0)); EXPECT_FALSE(testlib_memoryexists(p + gransz * 1)); EXPECT_TRUE(testlib_memoryexists(p + gransz * 2)); EXPECT_SYS(0, 0, munmap(p, gransz)); + EXPECT_EQ(count - 1, __maps.count); + count = __maps.count; EXPECT_SYS(0, 0, munmap(p + gransz * 2, gransz)); + EXPECT_EQ(count - 1, __maps.count); + count = __maps.count; EXPECT_FALSE(testlib_memoryexists(p + gransz * 0)); EXPECT_FALSE(testlib_memoryexists(p + gransz * 1)); EXPECT_FALSE(testlib_memoryexists(p + gransz * 2)); } +TEST(munmap, fillHoleInMemory) { + if (IsWindows()) + return; // needs fungible memory + int count = __maps.count; + char *base = __maps_randaddr(); + EXPECT_EQ(base + gransz * 0, + mmap(base + gransz * 0, gransz, PROT_READ | PROT_WRITE, + MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)); + EXPECT_EQ(count + 1, __maps.count); + count = __maps.count; + EXPECT_TRUE(testlib_memoryexists(base + gransz * 0)); + EXPECT_FALSE(testlib_memoryexists(base + gransz * 1)); + EXPECT_FALSE(testlib_memoryexists(base + gransz * 2)); + EXPECT_EQ(base + gransz * 2, + mmap(base + gransz * 2, gransz, PROT_READ | PROT_WRITE, + MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)); + EXPECT_EQ(count + 1, __maps.count); + count = __maps.count; + EXPECT_TRUE(testlib_memoryexists(base + gransz * 0)); + EXPECT_FALSE(testlib_memoryexists(base + gransz * 1)); + EXPECT_TRUE(testlib_memoryexists(base + gransz * 2)); + EXPECT_EQ(base + gransz * 1, + mmap(base + gransz * 1, gransz, PROT_READ | PROT_WRITE, + MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)); + EXPECT_EQ(count - 1, __maps.count); + count = __maps.count; + EXPECT_TRUE(testlib_memoryexists(base + gransz * 0)); + EXPECT_TRUE(testlib_memoryexists(base + gransz * 1)); + EXPECT_TRUE(testlib_memoryexists(base + gransz * 2)); + EXPECT_SYS(0, 0, munmap(base, gransz * 3)); + EXPECT_EQ(count - 1, __maps.count); + count = __maps.count; + EXPECT_FALSE(testlib_memoryexists(base + gransz * 0)); + EXPECT_FALSE(testlib_memoryexists(base + gransz * 1)); + EXPECT_FALSE(testlib_memoryexists(base + gransz * 2)); +} + TEST(munmap, memoryHasHole) { if (IsWindows()) return; // needs carving diff --git a/test/libc/intrin/pthread_mutex_lock2_test.c b/test/libc/intrin/pthread_mutex_lock2_test.c index 93224da84..b530ac04b 100644 --- a/test/libc/intrin/pthread_mutex_lock2_test.c +++ b/test/libc/intrin/pthread_mutex_lock2_test.c @@ -40,7 +40,7 @@ pthread_mutexattr_t attr; FIXTURE(pthread_mutex_lock, normal) { ASSERT_EQ(0, pthread_mutexattr_init(&attr)); - ASSERT_EQ(0, pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL)); + ASSERT_EQ(0, pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT)); ASSERT_EQ(0, pthread_mutex_init(&lock, &attr)); ASSERT_EQ(0, pthread_mutexattr_destroy(&attr)); } @@ -79,7 +79,7 @@ TEST(pthread_mutex_lock, contention) { int i; pthread_t *th = gc(malloc(sizeof(pthread_t) * THREADS)); pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); pthread_mutex_init(&lock, &attr); pthread_mutexattr_destroy(&attr); count = 0; @@ -128,7 +128,7 @@ BENCH(pthread_mutex_lock, bench_uncontended) { pthread_mutex_t m; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); pthread_mutex_init(&m, &attr); EZBENCH2("normal 1x", donothing, BenchLockUnlock(&m)); } @@ -226,7 +226,7 @@ BENCH(pthread_mutex_lock, bench_contended) { pthread_mutex_t m; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); pthread_mutex_init(&m, &attr); struct MutexContentionArgs a = {&m}; pthread_create(&t, 0, MutexContentionWorker, &a); diff --git a/test/libc/intrin/pthread_mutex_lock_test.c b/test/libc/intrin/pthread_mutex_lock_test.c index 4881733f5..0a5514a98 100644 --- a/test/libc/intrin/pthread_mutex_lock_test.c +++ b/test/libc/intrin/pthread_mutex_lock_test.c @@ -20,12 +20,16 @@ #include "libc/atomic.h" #include "libc/calls/calls.h" #include "libc/calls/state.internal.h" +#include "libc/calls/struct/sigaction.h" +#include "libc/cosmo.h" #include "libc/errno.h" +#include "libc/intrin/kprintf.h" #include "libc/intrin/strace.h" #include "libc/log/check.h" #include "libc/macros.h" #include "libc/math.h" #include "libc/mem/gc.h" +#include "libc/mem/leaks.h" #include "libc/mem/mem.h" #include "libc/runtime/internal.h" #include "libc/runtime/runtime.h" @@ -34,6 +38,7 @@ #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/prot.h" #include "libc/sysv/consts/rlimit.h" +#include "libc/sysv/consts/sig.h" #include "libc/testlib/ezbench.h" #include "libc/testlib/testlib.h" #include "libc/thread/thread.h" @@ -48,16 +53,38 @@ int count; atomic_int started; atomic_int finished; +pthread_mutex_t lock; pthread_mutex_t mylock; pthread_spinlock_t slock; pthread_t th[THREADS]; +void ignore_signal(int sig) { +} + void SetUpOnce(void) { ASSERT_SYS(0, 0, pledge("stdio rpath", 0)); + kprintf("running %s\n", program_invocation_name); + signal(SIGTRAP, ignore_signal); +} + +TEST(pthread_mutex_lock, default) { + pthread_mutexattr_t attr; + ASSERT_EQ(0, pthread_mutexattr_init(&attr)); + ASSERT_EQ(0, pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT)); + ASSERT_EQ(0, pthread_mutex_init(&lock, &attr)); + ASSERT_EQ(0, pthread_mutexattr_destroy(&attr)); + ASSERT_EQ(0, pthread_mutex_init(&lock, 0)); + ASSERT_EQ(0, pthread_mutex_lock(&lock)); + ASSERT_EQ(EBUSY, pthread_mutex_trylock(&lock)); + ASSERT_EQ(0, pthread_mutex_unlock(&lock)); + ASSERT_EQ(0, pthread_mutex_trylock(&lock)); + ASSERT_EQ(0, pthread_mutex_unlock(&lock)); + ASSERT_EQ(0, pthread_mutex_lock(&lock)); + ASSERT_EQ(0, pthread_mutex_unlock(&lock)); + ASSERT_EQ(0, pthread_mutex_destroy(&lock)); } TEST(pthread_mutex_lock, normal) { - pthread_mutex_t lock; pthread_mutexattr_t attr; ASSERT_EQ(0, pthread_mutexattr_init(&attr)); ASSERT_EQ(0, pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL)); @@ -75,7 +102,6 @@ TEST(pthread_mutex_lock, normal) { } TEST(pthread_mutex_lock, recursive) { - pthread_mutex_t lock; pthread_mutexattr_t attr; ASSERT_EQ(0, pthread_mutexattr_init(&attr)); ASSERT_EQ(0, pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)); @@ -99,15 +125,15 @@ TEST(pthread_mutex_lock, recursive) { } TEST(pthread_mutex_lock, errorcheck) { - pthread_mutex_t lock; pthread_mutexattr_t attr; ASSERT_EQ(0, pthread_mutexattr_init(&attr)); ASSERT_EQ(0, pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK)); ASSERT_EQ(0, pthread_mutex_init(&lock, &attr)); ASSERT_EQ(0, pthread_mutexattr_destroy(&attr)); ASSERT_EQ(0, pthread_mutex_lock(&lock)); + ASSERT_EQ(1, __deadlock_tracked(&lock)); ASSERT_EQ(EDEADLK, pthread_mutex_lock(&lock)); - ASSERT_EQ(EDEADLK, pthread_mutex_trylock(&lock)); + ASSERT_EQ(EBUSY, pthread_mutex_trylock(&lock)); ASSERT_EQ(0, pthread_mutex_unlock(&lock)); ASSERT_EQ(0, pthread_mutex_destroy(&lock)); } @@ -130,7 +156,7 @@ TEST(pthread_mutex_lock, contention) { int i; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); pthread_mutex_init(&mylock, &attr); pthread_mutexattr_destroy(&attr); count = 0; diff --git a/libc/intrin/directmap.c b/test/libc/intrin/stack_test.c similarity index 52% rename from libc/intrin/directmap.c rename to test/libc/intrin/stack_test.c index b0a40ff59..e07a2d7fc 100644 --- a/libc/intrin/directmap.c +++ b/test/libc/intrin/stack_test.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ Copyright 2024 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,51 +16,60 @@ │ 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/syscall-sysv.internal.h" +#include "libc/cosmo.h" #include "libc/dce.h" #include "libc/errno.h" -#include "libc/intrin/describeflags.h" -#include "libc/intrin/directmap.h" -#include "libc/intrin/strace.h" -#include "libc/nt/runtime.h" -#include "libc/runtime/memtrack.internal.h" #include "libc/runtime/runtime.h" -#include "libc/runtime/syslib.internal.h" -#include "libc/sysv/errfuns.h" +#include "libc/sysv/consts/map.h" +#include "libc/sysv/consts/prot.h" +#include "libc/testlib/testlib.h" -/** - * Obtains memory mapping directly from system. - * - * The mmap() function needs to track memory mappings in order to - * support Windows NT and Address Sanitizer. That memory tracking can be - * bypassed by calling this function. However the caller is responsible - * for passing the magic memory handle on Windows NT to CloseHandle(). - * - * @asyncsignalsafe - */ -struct DirectMap sys_mmap(void *addr, size_t size, int prot, int flags, int fd, - int64_t off) { - struct DirectMap d; - if ((__virtualsize += size) >= __virtualmax) { - d.maphandle = kNtInvalidHandleValue; - d.addr = (void *)enomem(); - } else if (IsXnuSilicon()) { - long p = _sysret(__syslib->__mmap(addr, size, prot, flags, fd, off)); - d.maphandle = kNtInvalidHandleValue; - d.addr = (void *)p; - } else if (!IsWindows() && !IsMetal()) { - d.addr = __sys_mmap(addr, size, prot, flags, fd, off, off); - d.maphandle = kNtInvalidHandleValue; - } else if (IsMetal()) { - d = sys_mmap_metal(addr, size, prot, flags, fd, off); - } else { - d = sys_mmap_nt(addr, size, prot, flags, fd, off); - } - if (d.addr == MAP_FAILED) - __virtualsize -= size; - KERNTRACE("sys_mmap(%.12p, %'zu, %s, %s, %d, %'ld) → {%.12p, %p}% m", addr, - size, DescribeProtFlags(prot), DescribeMapFlags(flags), fd, off, - d.addr, d.maphandle); - return d; +// returns true if byte at memory address is readable +bool readable(void *addr) { + return testlib_pokememory(addr); +} + +// returns true if page is reserved by linux memory manager +// it can be true for addresses that aren't listed in /proc/PID/maps +bool occupied(void *addr) { + int olde = errno; + char *want = (char *)((uintptr_t)addr & -__pagesize); + char *got = + __sys_mmap(want, __pagesize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0, 0); + if (got == MAP_FAILED) { + unassert(errno == IsFreebsd() ? EINVAL : EEXIST); + errno = olde; + return true; + } + sys_munmap(got, __pagesize); + return got != want; +} + +TEST(stack, test) { + if (IsWindows()) + return; + + void *vstackaddr; + size_t stacksize = 65536; + size_t guardsize = 4096; + unassert(!cosmo_stack_alloc(&stacksize, &guardsize, &vstackaddr)); + char *stackaddr = vstackaddr; + + /* check memory reservation */ + unassert(occupied(stackaddr + stacksize - 1)); // top stack + unassert(occupied(stackaddr)); // bot stack + unassert(occupied(stackaddr - 1)); // top guard + unassert(occupied(stackaddr - guardsize)); // bot guard + + /* check memory accessibility */ + unassert(readable(stackaddr + stacksize - 1)); // top stack + unassert(readable(stackaddr)); // bot stack + unassert(!readable(stackaddr - 1)); // top guard + unassert(!readable(stackaddr - guardsize)); // bot guard + + unassert(!cosmo_stack_free(stackaddr, stacksize, guardsize)); } diff --git a/test/libc/intrin/tree_test.c b/test/libc/intrin/tree_test.c index 7bafb37dd..fbddf18f7 100644 --- a/test/libc/intrin/tree_test.c +++ b/test/libc/intrin/tree_test.c @@ -178,6 +178,9 @@ void search_test(void) { // ↑ ↑ ↑ // 4 3 8 // + x = tree_floor(tree, (void *)0l, number_search); + if (x) + exit(4); x = tree_floor(tree, (void *)4l, number_search); if (!x) exit(4); diff --git a/test/libc/log/BUILD.mk b/test/libc/log/BUILD.mk index 5850736bf..ee7bf2c1e 100644 --- a/test/libc/log/BUILD.mk +++ b/test/libc/log/BUILD.mk @@ -5,14 +5,8 @@ PKGS += TEST_LIBC_LOG TEST_LIBC_LOG_SRCS := $(wildcard test/libc/log/*.c) TEST_LIBC_LOG_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_LOG_SRCS)) - -TEST_LIBC_LOG_OBJS = \ - $(TEST_LIBC_LOG_SRCS:%.c=o/$(MODE)/%.o) \ - o/$(MODE)/test/libc/log/backtrace.zip.o \ - o/$(MODE)/test/libc/log/backtrace.dbg.zip.o - -TEST_LIBC_LOG_COMS = \ - $(TEST_LIBC_LOG_SRCS:%.c=o/$(MODE)/%) +TEST_LIBC_LOG_OBJS = $(TEST_LIBC_LOG_SRCS:%.c=o/$(MODE)/%.o) +TEST_LIBC_LOG_COMS = $(TEST_LIBC_LOG_SRCS:%.c=o/$(MODE)/%) TEST_LIBC_LOG_BINS = \ $(TEST_LIBC_LOG_COMS) \ @@ -26,19 +20,17 @@ TEST_LIBC_LOG_CHECKS = \ TEST_LIBC_LOG_DIRECTDEPS = \ LIBC_CALLS \ - LIBC_RUNTIME \ - NET_HTTP \ - LIBC_STDIO \ - LIBC_X \ - LIBC_INTRIN \ LIBC_FMT \ + LIBC_INTRIN \ + LIBC_LOG \ LIBC_MEM \ LIBC_NEXGEN32E \ - LIBC_LOG \ + LIBC_PROC \ + LIBC_RUNTIME \ + LIBC_STDIO \ LIBC_STR \ - LIBC_TESTLIB \ LIBC_SYSV \ - LIBC_LOG + LIBC_TESTLIB \ TEST_LIBC_LOG_DEPS := \ $(call uniq,$(foreach x,$(TEST_LIBC_LOG_DIRECTDEPS),$($(x)))) @@ -56,29 +48,6 @@ o/$(MODE)/test/libc/log/%.dbg: \ $(APE_NO_MODIFY_SELF) @$(APELINK) -o/$(MODE)/test/libc/log/backtrace_test.dbg: \ - $(TEST_LIBC_LOG_DEPS) \ - o/$(MODE)/test/libc/log/backtrace.zip.o \ - o/$(MODE)/test/libc/log/backtrace.dbg.zip.o \ - o/$(MODE)/test/libc/log/backtrace_test.o \ - o/$(MODE)/test/libc/log/log.pkg \ - $(LIBC_TESTMAIN) \ - $(CRT) \ - $(APE_NO_MODIFY_SELF) - @$(APELINK) - -o/$(MODE)/test/libc/log/backtrace.dbg: \ - $(TEST_LIBC_LOG_DEPS) \ - o/$(MODE)/test/libc/log/backtrace.o \ - $(CRT) \ - $(APE_NO_MODIFY_SELF) - @$(APELINK) - -o/$(MODE)/test/libc/log/backtrace.zip.o \ -o/$(MODE)/test/libc/log/backtrace.dbg.zip.o: private \ - ZIPOBJ_FLAGS += \ - -B - .PHONY: o/$(MODE)/test/libc/log o/$(MODE)/test/libc/log: \ $(TEST_LIBC_LOG_BINS) \ diff --git a/test/libc/log/backtrace.c b/test/libc/log/backtrace.c deleted file mode 100644 index 09b858a8f..000000000 --- a/test/libc/log/backtrace.c +++ /dev/null @@ -1,154 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│ vi: set et 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/fmt/conv.h" -#include "libc/intrin/weaken.h" -#include "libc/limits.h" -#include "libc/log/log.h" -#include "libc/macros.h" -#include "libc/mem/leaks.h" -#include "libc/mem/mem.h" -#include "libc/runtime/internal.h" -#include "libc/runtime/symbols.internal.h" -#include "libc/stdio/stdio.h" -#include "libc/str/str.h" -#ifdef __x86_64__ - -#include - -int StackOverflow(int d) { - char A[8]; - for (int i = 0; i < sizeof(A); i++) - A[i] = d + i; - if (__veil("r", d)) - return StackOverflow(d + 1) + A[d % sizeof(A)]; - return 0; -} - -void FpuCrash(void) { - typedef char xmm_t __attribute__((__vector_size__(16))); - xmm_t v = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, - 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}; - volatile int x = 0; - asm volatile("fldpi"); - asm volatile("mov\t%0,%%r15" : /* no outputs */ : "g"(0x3133731337)); - asm volatile("movaps\t%0,%%xmm15" : /* no outputs */ : "x"(v)); - fputc(7 / x, stdout); -} - -char bss[10]; -void BssOverrunCrash(int n) { - int i; - for (i = 0; i < n; ++i) { - bss[i] = i; - } -} - -char data[10] = "abcdeabcde"; -void DataOverrunCrash(int n) { - int i; - for (i = 0; i < n; ++i) { - data[i] = i; - } -} - -const char rodata[10] = "abcdeabcde"; -int RodataOverrunCrash(int i) { - return rodata[i]; -} - -char *StackOverrunCrash(int n) { - int i; - char stack[10]; - bzero(stack, sizeof(stack)); - for (i = 0; i < n; ++i) { - stack[i] = i; - } - return strdup(stack); -} - -char *MemoryLeakCrash(void) { - char *p = strdup("doge"); - CheckForMemoryLeaks(); - return p; -} - -int NpeCrash(char *p) { - asm("nop"); // xxx: due to backtrace addr-1 thing - return *p; -} - -int StackOverflowCrash(int d) { - char A[8]; - for (int i = 0; i < sizeof(A); i++) - A[i] = d + i; - if (__veil("r", d)) - return StackOverflowCrash(d + 1) + A[d % sizeof(A)]; - return 0; -} - -void (*pFpuCrash)(void) = FpuCrash; -void (*pBssOverrunCrash)(int) = BssOverrunCrash; -void (*pDataOverrunCrash)(int) = DataOverrunCrash; -int (*pRodataOverrunCrash)(int) = RodataOverrunCrash; -char *(*pMemoryLeakCrash)(void) = MemoryLeakCrash; -int (*pNpeCrash)(char *) = NpeCrash; - -int main(int argc, char *argv[]) { - ShowCrashReports(); - if (argc > 1) { - switch (atoi(argv[1])) { - case 0: - break; - case 1: - pFpuCrash(); - exit(0); - case 2: - pBssOverrunCrash(10 + 1); - exit(0); - case 3: - exit(pRodataOverrunCrash(10 + 1)); - case 4: - pDataOverrunCrash(10 + 1); - exit(0); - case 5: - exit(StackOverflowCrash(0)); - case 6: - exit((intptr_t)pMemoryLeakCrash()); - case 7: - exit(pNpeCrash(0)); - case 8: - exit(pNpeCrash(0)); - case 9: - exit(StackOverflow(0)); - default: - fputs("error: unrecognized argument\n", stderr); - exit(1); - } - } else { - fputs("error: too few args\n", stderr); - exit(1); - } -} - -#else - -int main(int argc, char *argv[]) { -} - -#endif /* __x86_64__ */ diff --git a/test/libc/log/backtrace_test.c b/test/libc/log/backtrace_test.c deleted file mode 100644 index ab735e4cf..000000000 --- a/test/libc/log/backtrace_test.c +++ /dev/null @@ -1,402 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│ vi: set et 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/assert.h" -#include "libc/calls/calls.h" -#include "libc/dce.h" -#include "libc/errno.h" -#include "libc/fmt/conv.h" -#include "libc/limits.h" -#include "libc/log/libfatal.internal.h" -#include "libc/log/log.h" -#include "libc/mem/gc.h" -#include "libc/mem/mem.h" -#include "libc/runtime/internal.h" -#include "libc/runtime/runtime.h" -#include "libc/runtime/symbols.internal.h" -#include "libc/stdio/append.h" -#include "libc/stdio/stdio.h" -#include "libc/str/str.h" -#include "libc/sysv/consts/o.h" -#include "libc/sysv/consts/sig.h" -#include "libc/testlib/testlib.h" -#include "libc/x/xasprintf.h" -#include "net/http/escape.h" -#ifdef __x86_64__ - -#if 0 -__static_yoink("backtrace"); -__static_yoink("backtrace.dbg"); - -void SetUpOnce(void) { - testlib_enable_tmp_setup_teardown_once(); - ASSERT_NE(-1, mkdir("bin", 0755)); - testlib_extract("/zip/backtrace", "bin/backtrace", 0755); - testlib_extract("/zip/backtrace.dbg", "bin/backtrace.dbg", 0755); -} - -static bool OutputHasSymbol(const char *output, const char *s) { - return strstr(output, s) || (!FindDebugBinary() && strstr(output, "NULL")); -} - -// UNFREED MEMORY -// o/dbg/test/libc/log/backtrace_test -// max allocated space 655,360 -// total allocated space 80 -// total free space 327,600 -// releasable space 0 -// mmaped space 65,360 -// non-mmapped space 327,680 -// -// 100080040020 64 bytes 5 used -// 421871 strdup -// 416529 MemoryLeakCrash -// 41666d SetUp -// 45428c testlib_runtestcases -// -// 00007fff0000-000080010000 rw-pa-F 2x shadow of 000000000000 -// 000080070000-0000800a0000 rw-pa-F 3x shadow of 0000003c0000 -// 02008fff0000-020090020000 rw-pa-F 3x shadow of 10007ffc0000 -// 020090060000-020090080000 rw-pa-F 2x shadow of 100080340000 -// 0e007fff0000-0e0080010000 rw-pa-F 2x shadow of 6ffffffc0000 -// 100006560000-100006580000 rw-pa-F 2x shadow of 7ffc32b40000 -// 100080000000-100080050000 rw-pa-- 5x automap w/ 50 frame hole -// 100080370000-100080390000 rw-pa-- 2x automap w/ 1 frame hole -// 1000803a0000-1000803b0000 rw-pa-- 1x automap -// 6ffffffe0000-700000000000 rw-paSF 2x stack -// # 24 frames mapped w/ 51 frames gapped -TEST(ShowCrashReports, testMemoryLeakCrash) { - size_t got; - ssize_t rc; - int ws, pid, fds[2]; - char *output, buf[512]; - ASSERT_NE(-1, pipe2(fds, O_CLOEXEC)); - ASSERT_NE(-1, (pid = fork())); - if (!pid) { - dup2(fds[1], 1); - dup2(fds[1], 2); - execv("bin/backtrace", (char *const[]){"bin/backtrace", "6", 0}); - _Exit(127); - } - close(fds[1]); - output = 0; - appends(&output, ""); - for (;;) { - rc = read(fds[0], buf, sizeof(buf)); - if (rc == -1) { - ASSERT_EQ(EINTR, errno); - continue; - } - if ((got = rc)) { - appendd(&output, buf, got); - } else { - break; - } - } - close(fds[0]); - ASSERT_NE(-1, wait(&ws)); - // tinyprint(2, gc(IndentLines(output, -1, 0, 4)), "\n", NULL); - EXPECT_EQ(78 << 8, ws); - ASSERT_TRUE(!!strstr(output, "UNFREED MEMORY")); - if (IsAsan()) { - ASSERT_TRUE(OutputHasSymbol(output, "strdup") && - OutputHasSymbol(output, "MemoryLeakCrash")); - } - free(output); -} - -// error: Uncaught SIGFPE (FPE_INTDIV) on nightmare pid 11724 -// /home/jart/cosmo/o/dbg/test/libc/log/backtrace_test.tmp.11721 -// ENOTTY[25] -// Linux nightmare SMP Thu, 12 Aug 2021 06:16:45 UTC -// -// 0x0000000000414659: FpuCrash at test/libc/log/backtrace_test.c:35 -// 0x000000000045003b: testlib_runtestcases at libc/testlib/testrunner.c:98 -// 0x000000000044b770: testlib_runalltests at libc/testlib/runner.c:37 -// 0x000000000040278e: main at libc/testlib/testmain.c:86 -// 0x0000000000403210: cosmo at libc/runtime/cosmo.S:65 -// 0x0000000000402247: _start at libc/crt/crt.S:67 -// -// RAX 0000000000000007 RBX 00006fffffffff10 RDI 00007ffe0745fde1 ST(0) 0.0 -// RCX 0000000000000000 RDX 0000000000000000 RSI 0000000000489900 ST(1) 0.0 -// RBP 00006fffffffff70 RSP 00006fffffffff10 RIP 000000000041465a ST(2) 0.0 -// R8 0000000000000001 R9 00006ffffffffcc0 R10 00006ffffffffe60 ST(3) 0.0 -// R11 000000000000000d R12 00000dffffffffe2 R13 00006fffffffff10 ST(4) 0.0 -// R14 0000000000000003 R15 000000000049b700 VF PF ZF IF -// -// XMM0 00000000000000000000000000000000 XMM8 00000000000000000000000000000000 -// XMM1 000000008000000400000000004160ea XMM9 00000000000000000000000000000000 -// XMM2 00000000000000000000000000000000 XMM10 00000000000000000000000000000000 -// XMM3 00000000000000000000000000000000 XMM11 00000000000000000000000000000000 -// XMM4 00000000000000000000000000000000 XMM12 00000000000000000000000000000000 -// XMM5 00000000000000000000000000000000 XMM13 00000000000000000000000000000000 -// XMM6 00000000000000000000000000000000 XMM14 00000000000000000000000000000000 -// XMM7 00000000000000000000000000000000 XMM15 00000000000000000000000000000000 -// -// mm->i == 4; -// mm->p[ 0]=={0x00008007,0x00008008,-1,3,50}; /* 2 */ -// /* 234,881,012 */ -// mm->p[ 1]=={0x0e007ffd,0x0e007fff,-1,3,50}; /* 3 */ -// /* 33,538,280 */ -// mm->p[ 2]=={0x100040e8,0x100040e8,-1,3,50}; /* 1 */ -// /* 1,610,596,103 */ -// mm->p[ 3]=={0x6ffffff0,0x6fffffff,12884901888,306,0}; /* 16 */ -// /* 22 frames mapped w/ 1,879,015,395 frames gapped */ -// -// 00400000-0045b000 r-xp 00000000 08:03 4587526 -// /home/jart/cosmo/o/dbg/test/libc/log/backtrace_test.tmp.11721 -// 0045b000-00461000 rw-p 0005b000 08:03 4587526 -// /home/jart/cosmo/o/dbg/test/libc/log/backtrace_test.tmp.11721 -// 00461000-004a0000 rw-p 00000000 00:00 0 -// 80070000-80090000 rw-p 00000000 00:00 0 -// e007ffd0000-e0080000000 rw-p 00000000 00:00 0 -// 100040e80000-100040e90000 rw-p 00000000 00:00 0 -// 6ffffff00000-700000000000 rw-p 00000000 00:00 0 -// 7ffe0743f000-7ffe07460000 rw-p 00000000 00:00 0 [stack] -// 7ffe075a8000-7ffe075ab000 r--p 00000000 00:00 0 [vvar] -// 7ffe075ab000-7ffe075ac000 r-xp 00000000 00:00 0 [vdso] -// -// /home/jart/cosmo/o/dbg/test/libc/log/backtrace_test.tmp.11721 1 -TEST(ShowCrashReports, testDivideByZero) { - size_t got; - ssize_t rc; - int ws, pid, fds[2]; - char *output, buf[512]; - ASSERT_NE(-1, pipe2(fds, O_CLOEXEC)); - ASSERT_NE(-1, (pid = fork())); - if (!pid) { - dup2(fds[1], 1); - dup2(fds[1], 2); - execv("bin/backtrace", (char *const[]){"bin/backtrace", "1", 0}); - _Exit(127); - } - close(fds[1]); - output = 0; - appends(&output, ""); - for (;;) { - rc = read(fds[0], buf, sizeof(buf)); - if (rc == -1) { - ASSERT_EQ(EINTR, errno); - continue; - } - if ((got = rc)) { - appendd(&output, buf, got); - } else { - break; - } - } - close(fds[0]); - ASSERT_NE(-1, wait(&ws)); - // tinyprint(2, gc(IndentLines(output, -1, 0, 4)), "\n", NULL); - if (IsModeDbg()) { - EXPECT_EQ(77 << 8, ws); - } else { - EXPECT_TRUE(WIFSIGNALED(ws)); - EXPECT_EQ(SIGFPE, WTERMSIG(ws)); - } - /* NULL is stopgap until we can copy symbol tables into binary */ -#ifdef __FNO_OMIT_FRAME_POINTER__ - ASSERT_TRUE(OutputHasSymbol(output, "FpuCrash")); -#endif - if (strstr(output, "divrem overflow")) { - // UBSAN handled it - } else { - // ShowCrashReports() handled it - if (!strstr(output, gc(xasprintf("%d", pid)))) { - fprintf(stderr, "ERROR: crash report didn't have pid\n%s\n", - gc(IndentLines(output, -1, 0, 4))); - __die(); - } - if (!strstr(output, "SIGFPE")) { - fprintf(stderr, "ERROR: crash report didn't have signal name\n%s\n", - gc(IndentLines(output, -1, 0, 4))); - __die(); - } - // XXX: WSL doesn't save and restore x87 registers to ucontext_t - if (!__iswsl1()) { - if (!strstr(output, "3.141")) { - fprintf(stderr, "ERROR: crash report didn't have fpu register\n%s\n", - gc(IndentLines(output, -1, 0, 4))); - __die(); - } - } - if (!strstr(output, "0f0e0d0c0b0a09080706050403020100")) { - fprintf(stderr, "ERROR: crash report didn't have sse register\n%s\n", - gc(IndentLines(output, -1, 0, 4))); - __die(); - } - if (!strstr(output, "3133731337")) { - fprintf(stderr, "ERROR: crash report didn't have general register\n%s\n", - gc(IndentLines(output, -1, 0, 4))); - __die(); - } - } - free(output); -} - -TEST(ShowCrashReports, testBssOverrunCrash) { - if (!IsAsan()) return; - size_t got; - ssize_t rc; - int ws, pid, fds[2]; - char *output, buf[512]; - ASSERT_NE(-1, pipe2(fds, O_CLOEXEC)); - ASSERT_NE(-1, (pid = fork())); - if (!pid) { - dup2(fds[1], 1); - dup2(fds[1], 2); - execv("bin/backtrace", (char *const[]){"bin/backtrace", "2", 0}); - _Exit(127); - } - close(fds[1]); - output = 0; - appends(&output, ""); - for (;;) { - rc = read(fds[0], buf, sizeof(buf)); - if (rc == -1) { - ASSERT_EQ(EINTR, errno); - continue; - } - if ((got = rc)) { - appendd(&output, buf, got); - } else { - break; - } - } - close(fds[0]); - ASSERT_NE(-1, wait(&ws)); - // tinyprint(2, gc(IndentLines(output, -1, 0, 4)), "\n", NULL); - EXPECT_EQ(77 << 8, ws); - /* NULL is stopgap until we can copy symbol tablces into binary */ -#ifdef __FNO_OMIT_FRAME_POINTER__ - ASSERT_TRUE(OutputHasSymbol(output, "BssOverrunCrash")); -#endif - if (IsAsan()) { - ASSERT_TRUE( - !!strstr(output, "'int' index 10 into 'char [10]' out of bounds")); - } else { - ASSERT_TRUE(!!strstr(output, "☺☻♥♦♣♠•◘○")); - ASSERT_TRUE(!!strstr(output, "global redzone")); - } - free(output); -} - -TEST(ShowCrashReports, testDataOverrunCrash) { - if (!IsAsan()) return; - size_t got; - ssize_t rc; - int ws, pid, fds[2]; - char *output, buf[512]; - ASSERT_NE(-1, pipe2(fds, O_CLOEXEC)); - ASSERT_NE(-1, (pid = fork())); - if (!pid) { - dup2(fds[1], 1); - dup2(fds[1], 2); - execv("bin/backtrace", (char *const[]){"bin/backtrace", "4", 0}); - _Exit(127); - } - close(fds[1]); - output = 0; - appends(&output, ""); - for (;;) { - rc = read(fds[0], buf, sizeof(buf)); - if (rc == -1) { - ASSERT_EQ(EINTR, errno); - continue; - } - if ((got = rc)) { - appendd(&output, buf, got); - } else { - break; - } - } - close(fds[0]); - ASSERT_NE(-1, wait(&ws)); - // tinyprint(2, gc(IndentLines(output, -1, 0, 4)), "\n", NULL); - EXPECT_EQ(77 << 8, ws); - /* NULL is stopgap until we can copy symbol tablces into binary */ -#ifdef __FNO_OMIT_FRAME_POINTER__ - ASSERT_TRUE(OutputHasSymbol(output, "DataOverrunCrash")); -#endif - if (!strstr(output, "'int' index 10 into 'char [10]' out")) { // ubsan - ASSERT_TRUE(!!strstr(output, "☺☻♥♦♣♠•◘○")); // asan - ASSERT_TRUE(!!strstr(output, "global redzone")); // asan - } - free(output); -} - -TEST(ShowCrashReports, testNpeCrashAfterFinalize) { - /* - * this test makes sure we're not doing things like depending on - * environment variables after __cxa_finalize is called in cases - * where putenv() is used - */ - size_t got; - ssize_t rc; - int ws, pid, fds[2]; - char *output, buf[512]; - ASSERT_NE(-1, pipe2(fds, O_CLOEXEC)); - ASSERT_NE(-1, (pid = fork())); - if (!pid) { - dup2(fds[1], 1); - dup2(fds[1], 2); - execv("bin/backtrace", (char *const[]){"bin/backtrace", "8", 0}); - _Exit(127); - } - close(fds[1]); - output = 0; - appends(&output, ""); - for (;;) { - rc = read(fds[0], buf, sizeof(buf)); - if (rc == -1) { - ASSERT_EQ(EINTR, errno); - continue; - } - if ((got = rc)) { - appendd(&output, buf, got); - } else { - break; - } - } - close(fds[0]); - ASSERT_NE(-1, wait(&ws)); - // tinyprint(2, gc(IndentLines(output, -1, 0, 4)), "\n", NULL); - if (IsModeDbg()) { - EXPECT_EQ(77 << 8, ws); - } else { - EXPECT_TRUE(WIFSIGNALED(ws)); - EXPECT_EQ(SIGSEGV, WTERMSIG(ws)); - } - /* NULL is stopgap until we can copy symbol tables into binary */ - if (!strstr(output, IsAsan() ? "null pointer" : "Uncaught SIGSEGV (SEGV_")) { - fprintf(stderr, "ERROR: crash report didn't diagnose the problem\n%s\n", - gc(IndentLines(output, -1, 0, 4))); - __die(); - } -#ifdef __FNO_OMIT_FRAME_POINTER__ - if (!OutputHasSymbol(output, "NpeCrash")) { - fprintf(stderr, "ERROR: crash report didn't have backtrace\n%s\n", - gc(IndentLines(output, -1, 0, 4))); - __die(); - } -#endif - free(output); -} -#endif - -#endif /* __x86_64__ */ diff --git a/test/libc/mem/malloc_test.c b/test/libc/mem/malloc_test.c index 5e69b98ca..b1b7d2609 100644 --- a/test/libc/mem/malloc_test.c +++ b/test/libc/mem/malloc_test.c @@ -22,7 +22,6 @@ #include "libc/dce.h" #include "libc/errno.h" #include "libc/intrin/cxaatexit.h" -#include "libc/intrin/kprintf.h" #include "libc/intrin/safemacros.h" #include "libc/macros.h" #include "libc/mem/gc.h" @@ -162,7 +161,7 @@ void *bulk[1024]; void BulkFreeBenchSetup(void) { size_t i; for (i = 0; i < ARRAYLEN(bulk); ++i) { - bulk[i] = malloc(rand() % 64); + bulk[i] = rand() % 64 ? malloc(rand() % 64) : 0; } } diff --git a/test/libc/mem/malloc_torture_test.c b/test/libc/mem/malloc_torture_test.c index 40ae54934..f20c1dc20 100644 --- a/test/libc/mem/malloc_torture_test.c +++ b/test/libc/mem/malloc_torture_test.c @@ -19,6 +19,7 @@ #include "libc/calls/struct/timespec.h" #include "libc/intrin/safemacros.h" #include "libc/mem/gc.h" +#include "libc/mem/leaks.h" #include "libc/mem/mem.h" #include "libc/stdio/rand.h" #include "libc/stdio/stdio.h" @@ -33,8 +34,8 @@ void *Worker(void *arg) { for (int i = 0; i < ITERATIONS; ++i) { char *p; - ASSERT_NE(NULL, (p = malloc(lemur64() % SIZE))); - ASSERT_NE(NULL, (p = realloc(p, max(lemur64() % SIZE, 1)))); + ASSERT_NE(NULL, (p = malloc(rand() % SIZE))); + ASSERT_NE(NULL, (p = realloc(p, rand() % SIZE))); free(p); } return 0; @@ -48,6 +49,7 @@ TEST(malloc, torture) { printf("\nmalloc torture test w/ %d threads and %d iterations\n", n, ITERATIONS); SPAWN(fork); + AssertNoLocksAreHeld(); struct timespec t1 = timespec_real(); for (i = 0; i < n; ++i) ASSERT_EQ(0, pthread_create(t + i, 0, Worker, 0)); diff --git a/test/libc/proc/BUILD.mk b/test/libc/proc/BUILD.mk index 11f37d91d..4175234ec 100644 --- a/test/libc/proc/BUILD.mk +++ b/test/libc/proc/BUILD.mk @@ -29,15 +29,17 @@ TEST_LIBC_PROC_DIRECTDEPS = \ LIBC_MEM \ LIBC_NEXGEN32E \ LIBC_NT_KERNEL32 \ - LIBC_RUNTIME \ LIBC_PROC \ + LIBC_RUNTIME \ + LIBC_LOG \ + LIBC_STDIO \ LIBC_STR \ LIBC_SYSV \ LIBC_TESTLIB \ LIBC_THREAD \ LIBC_X \ THIRD_PARTY_MUSL \ - THIRD_PARTY_TR + THIRD_PARTY_TR \ TEST_LIBC_PROC_DEPS := \ $(call uniq,$(foreach x,$(TEST_LIBC_PROC_DIRECTDEPS),$($(x)))) @@ -59,6 +61,17 @@ o/$(MODE)/test/libc/proc/%.dbg: \ o/$(MODE)/test/libc/proc/posix_spawn_test.runs: \ private QUOTA += -M8192m +o/$(MODE)/test/libc/proc/fork_test.dbg: \ + $(TEST_LIBC_PROC_DEPS) \ + o/$(MODE)/test/libc/proc/fork_test.o \ + o/$(MODE)/test/libc/proc/proc.pkg \ + o/$(MODE)/tool/hello/life-pe.ape.zip.o \ + o/$(MODE)/test/libc/proc/life.zip.o \ + $(LIBC_TESTMAIN) \ + $(CRT) \ + $(APE_NO_MODIFY_SELF) + @$(APELINK) + o/$(MODE)/test/libc/proc/posix_spawn_test.dbg: \ $(TEST_LIBC_PROC_DEPS) \ o/$(MODE)/test/libc/proc/posix_spawn_test.o \ @@ -73,23 +86,12 @@ o/$(MODE)/test/libc/proc/posix_spawn_test.dbg: \ $(APE_NO_MODIFY_SELF) @$(APELINK) -o/$(MODE)/test/libc/proc/system_test.dbg: \ - $(TEST_LIBC_PROC_DEPS) \ - o/$(MODE)/test/libc/proc/system_test.o \ - o/$(MODE)/test/libc/proc/proc.pkg \ - o/$(MODE)/tool/build/echo.zip.o \ - o/$(MODE)/tool/build/cocmd.zip.o \ - o/$(MODE)/tool/build/false.zip.o \ - $(LIBC_TESTMAIN) \ - $(CRT) \ - $(APE_NO_MODIFY_SELF) - @$(APELINK) - o/$(MODE)/test/libc/proc/execve_test.dbg: \ $(TEST_LIBC_PROC_DEPS) \ o/$(MODE)/test/libc/proc/execve_test.o \ o/$(MODE)/test/libc/calls/life-nomod.zip.o \ o/$(MODE)/test/libc/proc/execve_test_prog1.zip.o \ + o/$(MODE)/test/libc/proc/execve_test_prog2.zip.o \ o/$(MODE)/test/libc/mem/prog/life.elf.zip.o \ o/$(MODE)/test/libc/mem/prog/sock.elf.zip.o \ o/$(MODE)/test/libc/proc/proc.pkg \ @@ -110,7 +112,16 @@ o/$(MODE)/test/libc/proc/fexecve_test.dbg: \ $(APE_NO_MODIFY_SELF) @$(APELINK) +o/$(MODE)/test/libc/proc/life.dbg: \ + $(TEST_LIBC_PROC_DEPS) \ + o/$(MODE)/test/libc/proc/life.o \ + $(CRT) \ + $(APE_NO_MODIFY_SELF) + @$(APELINK) + +o/$(MODE)/test/libc/proc/life.zip.o \ o/$(MODE)/test/libc/proc/execve_test_prog1.zip.o \ +o/$(MODE)/test/libc/proc/execve_test_prog2.zip.o \ o/$(MODE)/test/libc/proc/life-pe.zip.o: private \ ZIPOBJ_FLAGS += \ -B diff --git a/test/libc/proc/execve_test.c b/test/libc/proc/execve_test.c index 01573483e..7bfd7b102 100644 --- a/test/libc/proc/execve_test.c +++ b/test/libc/proc/execve_test.c @@ -53,12 +53,12 @@ TEST(execve, testArgPassing) { char ibuf[12], buf[8]; const char *prog = "./execve_test_prog1"; testlib_extract("/zip/execve_test_prog1", prog, 0755); + testlib_extract("/zip/execve_test_prog2", "execve_test_prog2", 0755); for (i = 0; i < N; ++i) { FormatInt32(ibuf, i); GenBuf(buf, i); SPAWN(vfork); - execve(prog, (char *const[]){(char *)prog, "-", ibuf, buf, 0}, - (char *const[]){0}); + execl(prog, prog, "-", ibuf, buf, NULL); kprintf("execve failed: %m\n"); EXITS(0); } diff --git a/test/libc/proc/execve_test_prog1.c b/test/libc/proc/execve_test_prog1.c index 5a1ea77e8..901c9b6bc 100644 --- a/test/libc/proc/execve_test_prog1.c +++ b/test/libc/proc/execve_test_prog1.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/fmt/conv.h" +#include "libc/runtime/runtime.h" #include "libc/str/str.h" void GenBuf(char buf[8], int x) { @@ -40,5 +41,15 @@ int main(int argc, char *argv[]) { tinyprint(2, "error: buf check failed\n", NULL); return 10; } + const char *prog = "./execve_test_prog2"; + if (!fork()) { + execl(prog, prog, NULL); + _Exit(127); + } + int ws; + if (wait(&ws) == -1) + return 30; + if (ws) + return 31; return 0; } diff --git a/libc/intrin/flushers.c b/test/libc/proc/execve_test_prog2.c similarity index 91% rename from libc/intrin/flushers.c rename to test/libc/proc/execve_test_prog2.c index 9ef0e0576..2369ec9c9 100644 --- a/libc/intrin/flushers.c +++ b/test/libc/proc/execve_test_prog2.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ Copyright 2024 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,7 +16,8 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/stdio/fflush.internal.h" +#include "libc/proc/proc.h" +#include "libc/testlib/testlib.h" -pthread_mutex_t __fflush_lock_obj; -struct StdioFlush __fflush; +int main(int argc, char *argv[]) { +} diff --git a/test/libc/proc/fork_test.c b/test/libc/proc/fork_test.c index 958fbbb2b..264f226d3 100644 --- a/test/libc/proc/fork_test.c +++ b/test/libc/proc/fork_test.c @@ -21,22 +21,32 @@ #include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/struct/timespec.h" +#include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" #include "libc/errno.h" +#include "libc/intrin/kprintf.h" #include "libc/log/check.h" #include "libc/macros.h" #include "libc/nexgen32e/rdtsc.h" +#include "libc/proc/posix_spawn.h" #include "libc/runtime/runtime.h" +#include "libc/stdio/stdio.h" #include "libc/str/str.h" #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/msync.h" #include "libc/sysv/consts/prot.h" #include "libc/sysv/consts/sig.h" +#include "libc/testlib/benchmark.h" #include "libc/testlib/ezbench.h" #include "libc/testlib/subprocess.h" #include "libc/testlib/testlib.h" +#include "libc/thread/thread.h" #include "libc/thread/tls.h" +void SetUpOnce(void) { + testlib_enable_tmp_setup_teardown(); +} + TEST(fork, testPipes) { int a, b; int ws, pid; @@ -63,32 +73,27 @@ TEST(fork, testSharedMemory) { int *sharedvar; int *privatevar; EXPECT_NE(MAP_FAILED, - (sharedvar = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, + (sharedvar = mmap(0, getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0))); EXPECT_NE(MAP_FAILED, - (privatevar = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, + (privatevar = mmap(0, getpagesize(), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0))); stackvar = 1; *sharedvar = 1; *privatevar = 1; EXPECT_NE(-1, (pid = fork())); if (!pid) { - EXPECT_EQ(NULL, getenv("_FORK")); ++stackvar; - ++*sharedvar; ++*privatevar; - msync((void *)ROUNDDOWN((intptr_t)&stackvar, getpagesize()), getpagesize(), - MS_SYNC); - EXPECT_NE(-1, msync(privatevar, getpagesize(), MS_SYNC)); - EXPECT_NE(-1, msync(sharedvar, getpagesize(), MS_SYNC)); + ++*sharedvar; _exit(0); } EXPECT_NE(-1, waitpid(pid, &ws, 0)); EXPECT_EQ(1, stackvar); EXPECT_EQ(2, *sharedvar); EXPECT_EQ(1, *privatevar); - EXPECT_NE(-1, munmap(sharedvar, getpagesize())); - EXPECT_NE(-1, munmap(privatevar, getpagesize())); + EXPECT_SYS(0, 0, munmap(sharedvar, getpagesize())); + EXPECT_SYS(0, 0, munmap(privatevar, getpagesize())); } static volatile bool gotsigusr1; @@ -103,8 +108,6 @@ static void OnSigusr2(int sig) { } TEST(fork, childToChild) { - if (IsWindows()) - return; // :'( sigset_t mask, oldmask; int ws, parent, child1, child2; gotsigusr1 = false; @@ -118,14 +121,20 @@ TEST(fork, childToChild) { sigprocmask(SIG_BLOCK, &mask, &oldmask); ASSERT_NE(-1, (child1 = fork())); if (!child1) { - kill(parent, SIGUSR2); - sigsuspend(0); + if (kill(parent, SIGUSR2)) { + kprintf("%s:%d: error: failed to kill parent: %m\n", __FILE__, __LINE__); + _Exit(1); + } + ASSERT_SYS(EINTR, -1, sigsuspend(0)); _Exit(!gotsigusr1); } - sigsuspend(0); + EXPECT_SYS(EINTR, -1, sigsuspend(0)); ASSERT_NE(-1, (child2 = fork())); if (!child2) { - kill(child1, SIGUSR1); + if (kill(child1, SIGUSR1)) { + kprintf("%s:%d: error: failed to kill child1: %m\n", __FILE__, __LINE__); + _Exit(1); + } _Exit(0); } ASSERT_NE(-1, wait(&ws)); @@ -142,16 +151,111 @@ TEST(fork, preservesTlsMemory) { EXITS(0); } -void ForkInSerial(void) { +TEST(fork, privateExtraPageData_getsCopiedByFork) { + char *p; + ASSERT_NE(MAP_FAILED, (p = mmap(0, 1, PROT_WRITE | PROT_READ, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0))); + p[0] = 1; + p[1] = 2; + SPAWN(fork); + ASSERT_EQ(1, p[0]); + ASSERT_EQ(2, p[1]); + EXITS(0); + ASSERT_SYS(0, 0, munmap(p, 1)); +} + +TEST(fork, sharedExtraPageData_getsResurrectedByFork) { + char *p; + ASSERT_NE(MAP_FAILED, (p = mmap(0, 1, PROT_WRITE | PROT_READ, + MAP_ANONYMOUS | MAP_SHARED, -1, 0))); + p[0] = 1; + p[1] = 2; + SPAWN(fork); + ASSERT_EQ(1, p[0]); + ASSERT_EQ(2, p[1]); + EXITS(0); + ASSERT_SYS(0, 0, munmap(p, 1)); +} + +#define CHECK_TERMSIG \ + if (WIFSIGNALED(ws)) { \ + kprintf("%s:%d: error: forked life subprocess terminated with %G\n", \ + __FILE__, __LINE__, WTERMSIG(ws)); \ + exit(1); \ + } + +void fork_wait_in_serial(void) { int pid, ws; ASSERT_NE(-1, (pid = fork())); if (!pid) _Exit(0); ASSERT_NE(-1, waitpid(pid, &ws, 0)); + CHECK_TERMSIG; ASSERT_TRUE(WIFEXITED(ws)); ASSERT_EQ(0, WEXITSTATUS(ws)); } -BENCH(fork, bench) { - EZBENCH2("fork a", donothing, ForkInSerial()); +void vfork_execl_wait_in_serial(void) { + int pid, ws; + ASSERT_NE(-1, (pid = vfork())); + if (!pid) { + execl("./life", "./life", NULL); + _Exit(127); + } + ASSERT_NE(-1, waitpid(pid, &ws, 0)); + CHECK_TERMSIG; + ASSERT_TRUE(WIFEXITED(ws)); + ASSERT_EQ(42, WEXITSTATUS(ws)); +} + +void vfork_wait_in_serial(void) { + int pid, ws; + ASSERT_NE(-1, (pid = vfork())); + if (!pid) + _Exit(0); + ASSERT_NE(-1, waitpid(pid, &ws, 0)); + CHECK_TERMSIG; + ASSERT_TRUE(WIFEXITED(ws)); + ASSERT_EQ(0, WEXITSTATUS(ws)); +} + +void sys_fork_wait_in_serial(void) { + int pid, ws; + ASSERT_NE(-1, (pid = sys_fork())); + if (!pid) + _Exit(0); + ASSERT_NE(-1, waitpid(pid, &ws, 0)); + CHECK_TERMSIG; + ASSERT_TRUE(WIFEXITED(ws)); + ASSERT_EQ(0, WEXITSTATUS(ws)); +} + +void posix_spawn_in_serial(void) { + int ws, pid; + char *prog = "./life"; + char *args[] = {prog, NULL}; + char *envs[] = {NULL}; + ASSERT_EQ(0, posix_spawn(&pid, prog, NULL, NULL, args, envs)); + ASSERT_NE(-1, waitpid(pid, &ws, 0)); + CHECK_TERMSIG; + ASSERT_TRUE(WIFEXITED(ws)); + ASSERT_EQ(42, WEXITSTATUS(ws)); +} + +TEST(fork, bench) { + if (IsWindows()) { + testlib_extract("/zip/life-pe.ape", "life", 0755); + } else { + testlib_extract("/zip/life", "life", 0755); + } + vfork_wait_in_serial(); + vfork_execl_wait_in_serial(); + posix_spawn_in_serial(); + BENCHMARK(10, 1, vfork_wait_in_serial()); + if (!IsWindows()) + BENCHMARK(10, 1, sys_fork_wait_in_serial()); + fork_wait_in_serial(); + BENCHMARK(10, 1, fork_wait_in_serial()); + BENCHMARK(10, 1, posix_spawn_in_serial()); + BENCHMARK(10, 1, vfork_execl_wait_in_serial()); } diff --git a/test/libc/proc/handkill_test.c b/test/libc/proc/handkill_test.c index 07284b4ee..a669eeb75 100644 --- a/test/libc/proc/handkill_test.c +++ b/test/libc/proc/handkill_test.c @@ -125,8 +125,6 @@ TEST(handkill, thread2thread_async) { } TEST(handkill, process_async) { - if (IsWindows()) - return; SPAWN(fork); shm->ready = true; while (!shm->got_signal) @@ -142,8 +140,6 @@ TEST(handkill, process_async) { } TEST(handkill, process_pause) { - if (IsWindows()) - return; SPAWN(fork); shm->ready = true; pause(); diff --git a/test/libc/proc/life.c b/test/libc/proc/life.c new file mode 100644 index 000000000..6c67c3b22 --- /dev/null +++ b/test/libc/proc/life.c @@ -0,0 +1,3 @@ +int main(int argc, char *argv[]) { + return 42; +} diff --git a/test/libc/proc/posix_spawn_test.c b/test/libc/proc/posix_spawn_test.c index 044c24dcf..809fc865d 100644 --- a/test/libc/proc/posix_spawn_test.c +++ b/test/libc/proc/posix_spawn_test.c @@ -37,7 +37,7 @@ #include "libc/limits.h" #include "libc/mem/gc.h" #include "libc/mem/mem.h" -#include "libc/proc/proc.internal.h" +#include "libc/proc/proc.h" #include "libc/runtime/internal.h" #include "libc/runtime/memtrack.internal.h" #include "libc/runtime/runtime.h" @@ -262,8 +262,6 @@ void EmptySigHandler(int sig) { } TEST(posix_spawn, etxtbsy) { - if (IsWindows()) - return; // can't deliver signals between processes if (IsXnu()) return; // they don't appear impacted by this race condition if (IsNetbsd()) diff --git a/test/libc/proc/sched_getaffinity_test.c b/test/libc/proc/sched_getaffinity_test.c index 0ca0d8d53..dad9c1b26 100644 --- a/test/libc/proc/sched_getaffinity_test.c +++ b/test/libc/proc/sched_getaffinity_test.c @@ -30,6 +30,8 @@ #include "libc/thread/thread.h" #include "libc/thread/thread2.h" +int disable_limit_process_to_single_cpu; + void SetUp(void) { if (!IsLinux() && !IsFreebsd() && !IsWindows()) { exit(0); @@ -90,7 +92,6 @@ __attribute__((__constructor__)) static void init(void) { } } -#ifdef __x86_64__ TEST(sched_setaffinity, isInheritedAcrossExecve) { cpu_set_t x; CPU_ZERO(&x); @@ -105,7 +106,6 @@ TEST(sched_setaffinity, isInheritedAcrossExecve) { EXPECT_TRUE(WIFEXITED(ws)); EXPECT_EQ(42, WEXITSTATUS(ws)); } -#endif /* __x86_64__ */ TEST(sched_getaffinity, getpid) { cpu_set_t x, y; diff --git a/test/libc/sock/socket_test.c b/test/libc/sock/socket_test.c index f79b0d7a7..ff161888c 100644 --- a/test/libc/sock/socket_test.c +++ b/test/libc/sock/socket_test.c @@ -18,8 +18,8 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/calls/internal.h" -#include "libc/intrin/fds.h" #include "libc/dce.h" +#include "libc/intrin/fds.h" #include "libc/intrin/kprintf.h" #include "libc/nt/winsock.h" #include "libc/runtime/runtime.h" @@ -141,8 +141,6 @@ TEST(socket, canBeInheritedByForkedWorker) { WAIT(exit, 0); } -#ifdef __x86_64__ - __attribute__((__constructor__)) static void StdioPro(int argc, char *argv[]) { if (argc >= 2 && !strcmp(argv[1], "StdioProg")) { ASSERT_EQ(NULL, getenv("__STDIO_SOCKETS")); @@ -184,5 +182,3 @@ TEST(socket, canBeUsedAsExecutedStdio) { EXPECT_SYS(0, 0, close(3)); WAIT(exit, 0); } - -#endif /* __x86_64__ */ diff --git a/test/libc/stdio/BUILD.mk b/test/libc/stdio/BUILD.mk index 78bd1138c..3cc6f6d5f 100644 --- a/test/libc/stdio/BUILD.mk +++ b/test/libc/stdio/BUILD.mk @@ -28,26 +28,27 @@ TEST_LIBC_STDIO_DIRECTDEPS = \ LIBC_CALLS \ LIBC_FMT \ LIBC_INTRIN \ + LIBC_LOG \ LIBC_MEM \ LIBC_NEXGEN32E \ LIBC_PROC \ LIBC_RUNTIME \ LIBC_STDIO \ LIBC_STR \ + LIBC_SYSTEM \ LIBC_SYSV \ - LIBC_TINYMATH \ LIBC_TESTLIB \ LIBC_THREAD \ - LIBC_LOG \ + LIBC_TINYMATH \ LIBC_X \ THIRD_PARTY_COMPILER_RT \ THIRD_PARTY_GDTOA \ THIRD_PARTY_MBEDTLS \ THIRD_PARTY_MUSL \ THIRD_PARTY_NSYNC \ + THIRD_PARTY_TZ \ THIRD_PARTY_ZLIB \ THIRD_PARTY_ZLIB_GZ \ - THIRD_PARTY_TZ TEST_LIBC_STDIO_DEPS := \ $(call uniq,$(foreach x,$(TEST_LIBC_STDIO_DIRECTDEPS),$($(x)))) @@ -66,16 +67,6 @@ o/$(MODE)/test/libc/stdio/%.dbg: \ $(APE_NO_MODIFY_SELF) @$(APELINK) -o/$(MODE)/test/libc/stdio/popen_test.dbg: \ - $(TEST_LIBC_STDIO_DEPS) \ - o/$(MODE)/test/libc/stdio/popen_test.o \ - o/$(MODE)/test/libc/stdio/stdio.pkg \ - o/$(MODE)/tool/build/echo.zip.o \ - $(LIBC_TESTMAIN) \ - $(CRT) \ - $(APE_NO_MODIFY_SELF) - @$(APELINK) - $(TEST_LIBC_STDIO_OBJS): private \ DEFAULT_CCFLAGS += \ -fno-builtin diff --git a/test/libc/stdio/fgetwc_test.c b/test/libc/stdio/fgetwc_test.c index e7a55ceff..4f6bd7ccd 100644 --- a/test/libc/stdio/fgetwc_test.c +++ b/test/libc/stdio/fgetwc_test.c @@ -16,7 +16,9 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/stdio/stdio.h" +#include "libc/intrin/weaken.h" +#include "libc/mem/mem.h" +#include "libc/stdio/internal.h" #include "libc/testlib/testlib.h" TEST(fgetwc, testAscii_oneChar) { diff --git a/test/libc/stdio/getentropy_test.c b/test/libc/stdio/getentropy_test.c index f23ff2d4d..f6d1acdc8 100644 --- a/test/libc/stdio/getentropy_test.c +++ b/test/libc/stdio/getentropy_test.c @@ -33,8 +33,6 @@ #include "libc/testlib/testlib.h" #include "libc/thread/thread.h" #ifndef __aarch64__ -// TODO(jart): Make this test less resource intensive. -// TODO(jart): Why can EINTR happen on Windows? atomic_int done; atomic_int ready; @@ -51,11 +49,9 @@ void *TortureWorker(void *arg) { ASSERT_SYS(0, 0, sigprocmask(SIG_SETMASK, &ss, 0)); ready = true; while (!done) { - if (!IsWindows()) - pthread_kill(parent, SIGUSR1); + pthread_kill(parent, SIGUSR1); usleep(1); - if (!IsWindows()) - pthread_kill(parent, SIGUSR2); + pthread_kill(parent, SIGUSR2); usleep(1); } return 0; @@ -100,8 +96,7 @@ TEST(getentropy, test) { } done = true; ASSERT_EQ(0, pthread_join(child, 0)); - if (!IsWindows()) - ASSERT_GT(gotsome, 0); + ASSERT_GT(gotsome, 0); } #endif /* __aarch64__ */ diff --git a/test/libc/stdio/snprintf_test.c b/test/libc/stdio/snprintf_test.c index a8f5c1117..0fb38489e 100644 --- a/test/libc/stdio/snprintf_test.c +++ b/test/libc/stdio/snprintf_test.c @@ -21,6 +21,70 @@ #include "libc/str/str.h" #include "libc/testlib/testlib.h" +static void check_single_double(const char *fmt, const char *expected_str, + double value) { + char buf[30] = {0}; + int i = snprintf(buf, sizeof(buf), fmt, value); + + ASSERT_GE(sizeof(buf), strlen(expected_str)); + ASSERT_EQ(strlen(expected_str), i); + ASSERT_STREQ(expected_str, buf); + while (i < sizeof(buf)) + ASSERT_EQ('\0', buf[i++]); +} + +static void check_single_long_double(const char *fmt, const char *expected_str, + long double value) { + char buf[30] = {0}; + int i = snprintf(buf, sizeof(buf), fmt, value); + + ASSERT_GE(sizeof(buf), strlen(expected_str)); + ASSERT_EQ(strlen(expected_str), i); + ASSERT_STREQ(expected_str, buf); + while (i < sizeof(buf)) + ASSERT_EQ('\0', buf[i++]); +} + +void check_single_long_double_arr_allowed( + const char *fmt, const char *allowed_strs[], long double value) { + char buf[30] = {0}; + int res = snprintf(buf, sizeof(buf), fmt, value); + + for (size_t i = 0; allowed_strs[i] != NULL; ++i) + if (strlen(allowed_strs[i]) == res && strcmp(allowed_strs[i], buf) == 0) + return; + + printf("Failed to find matching str for %`'s, allowed strs:\n", buf); + for (size_t i = 0; allowed_strs[i] != NULL; ++i) + printf("- %`'s\n", allowed_strs[i]); + fflush(stdout); + ASSERT_EQ(false, true); +} + +static void check_single_int(const char *fmt, const char *expected_str, + int value) { + char buf[30] = {0}; + int i = snprintf(buf, sizeof(buf), fmt, value); + + ASSERT_GE(sizeof(buf), strlen(expected_str)); + ASSERT_EQ(strlen(expected_str), i); + ASSERT_STREQ(expected_str, buf); + while (i < sizeof(buf)) + ASSERT_EQ('\0', buf[i++]); +} + +static void check_single_wint_t(const char *fmt, const char *expected_str, + wint_t value) { + char buf[30] = {0}; + int i = snprintf(buf, sizeof(buf), fmt, value); + + ASSERT_GE(sizeof(buf), strlen(expected_str)); + ASSERT_EQ(strlen(expected_str), i); + ASSERT_STREQ(expected_str, buf); + while (i < sizeof(buf)) + ASSERT_EQ('\0', buf[i++]); +} + TEST(snprintf, testVeryLargePrecision) { char buf[512] = {}; int i = snprintf(buf, sizeof(buf), "%.9999u", 10); @@ -30,59 +94,21 @@ TEST(snprintf, testVeryLargePrecision) { } TEST(snprintf, testPlusFlagOnChar) { - char buf[10] = {}; - int i = snprintf(buf, sizeof(buf), "%+c", '='); - - ASSERT_EQ(1, i); - ASSERT_STREQ("=", buf); + check_single_int("%+c", "=", '='); } TEST(snprintf, testInf) { - char buf[10] = {}; - int i = snprintf(buf, sizeof(buf), "%f", 1.0 / 0.0); - - ASSERT_EQ(3, i); - ASSERT_STREQ("inf", buf); - - memset(buf, '\0', 4); - i = snprintf(buf, sizeof(buf), "%Lf", 1.0L / 0.0L); - ASSERT_EQ(3, i); - ASSERT_STREQ("inf", buf); - - memset(buf, '\0', 4); - i = snprintf(buf, sizeof(buf), "%e", 1.0 / 0.0); - ASSERT_EQ(3, i); - ASSERT_STREQ("inf", buf); - - memset(buf, '\0', 4); - i = snprintf(buf, sizeof(buf), "%Le", 1.0L / 0.0L); - ASSERT_EQ(3, i); - ASSERT_STREQ("inf", buf); - - memset(buf, '\0', 4); - i = snprintf(buf, sizeof(buf), "%g", 1.0 / 0.0); - ASSERT_EQ(3, i); - ASSERT_STREQ("inf", buf); - - memset(buf, '\0', 4); - i = snprintf(buf, sizeof(buf), "%Lg", 1.0L / 0.0L); - ASSERT_EQ(3, i); - ASSERT_STREQ("inf", buf); - - for (i = 4; i < 10; ++i) - ASSERT_EQ('\0', buf[i]); + check_single_double("%f", "inf", 1.0 / 0.0); + check_single_long_double("%Lf", "inf", 1.0L / 0.0L); + check_single_double("%e", "inf", 1.0 / 0.0); + check_single_long_double("%Le", "inf", 1.0L / 0.0L); + check_single_double("%g", "inf", 1.0 / 0.0); + check_single_long_double("%Lg", "inf", 1.0L / 0.0L); } TEST(snprintf, testUppercaseCConversionSpecifier) { - char buf[10] = {}; - int i = snprintf(buf, sizeof(buf), "%C", L'a'); - - ASSERT_EQ(1, i); - ASSERT_STREQ("a", buf); - - i = snprintf(buf, sizeof(buf), "%C", L'☺'); - ASSERT_EQ(3, i); - ASSERT_STREQ("☺", buf); + check_single_wint_t("%C", "a", L'a'); + check_single_wint_t("%C", "☺", L'☺'); } // Make sure we don't va_arg the wrong argument size on wide character @@ -188,58 +214,26 @@ TEST(snprintf, testNConversionSpecifier) { } TEST(snprintf, testLongDoubleEConversionSpecifier) { - char buf[20] = {}; - int i = snprintf(buf, sizeof(buf), "%Le", 1234567.8L); - - ASSERT_EQ(12, i); - ASSERT_STREQ("1.234568e+06", buf); + check_single_long_double("%Le", "1.234568e+06", 1234567.8L); } TEST(snprintf, testLongDoubleRounding) { int previous_rounding = fegetround(); ASSERT_EQ(0, fesetround(FE_DOWNWARD)); - char buf[20]; - int i = snprintf(buf, sizeof(buf), "%.3Lf", 4.4375L); - ASSERT_EQ(5, i); - ASSERT_STREQ("4.437", buf); - - i = snprintf(buf, sizeof(buf), "%.3Lf", -4.4375L); - ASSERT_EQ(6, i); - ASSERT_STREQ("-4.438", buf); + check_single_long_double("%.3Lf", "4.437", 4.4375L); + check_single_long_double("%.3Lf", "-4.438", -4.4375L); ASSERT_EQ(0, fesetround(FE_TOWARDZERO)); - i = snprintf(buf, sizeof(buf), "%.3Lf", -4.4375L); - ASSERT_EQ(6, i); - ASSERT_STREQ("-4.437", buf); + check_single_long_double("%.3Lf", "-4.437", -4.4375L); ASSERT_EQ(0, fesetround(previous_rounding)); } -void check_a_conversion_specifier_double(const char *fmt, - const char *expected_str, - double value) { - char buf[30] = {0}; - int i = snprintf(buf, sizeof(buf), fmt, value); - - ASSERT_EQ(strlen(expected_str), i); - ASSERT_STREQ(expected_str, buf); -} - -void check_a_conversion_specifier_long_double(const char *fmt, - const char *expected_str, - long double value) { - char buf[30] = {0}; - int i = snprintf(buf, sizeof(buf), fmt, value); - - ASSERT_EQ(strlen(expected_str), i); - ASSERT_STREQ(expected_str, buf); -} - void check_a_conversion_specifier_double_prec_1(const char *expected_str, double value) { - check_a_conversion_specifier_double("%.1a", expected_str, value); + check_single_double("%.1a", expected_str, value); } TEST(snprintf, testAConversionSpecifierRounding) { @@ -265,20 +259,25 @@ TEST(snprintf, testAConversionSpecifier) { check_a_conversion_specifier_double_prec_1("0x1.ap+4", 0x1.98p+4); check_a_conversion_specifier_double_prec_1("0x1.ap+4", 0x1.a8p+4); - check_a_conversion_specifier_double("%#a", "0x0.p+0", 0x0.0p0); - check_a_conversion_specifier_double("%#A", "0X0.P+0", 0x0.0p0); - check_a_conversion_specifier_long_double("%#La", "0x0.p+0", 0x0.0p0L); - check_a_conversion_specifier_long_double("%#LA", "0X0.P+0", 0x0.0p0L); + check_single_double("%#a", "0x0.p+0", 0x0.0p0); + check_single_double("%#A", "0X0.P+0", 0x0.0p0); + check_single_long_double("%#La", "0x0.p+0", 0x0.0p0L); + check_single_long_double("%#LA", "0X0.P+0", 0x0.0p0L); - check_a_conversion_specifier_double("%.2a", "0x1.00p-1026", 0xf.fffp-1030); + check_single_double("%.2a", "0x1.00p-1026", 0xf.fffp-1030); - // TODO(GabrielRavier): fix me on aarch64 - /* check_a_conversion_specifier_long_double("%.1La", "0x1.0p+1", 1.999L); */ + check_single_double("%.1a", "0x2.0p+0", 1.999); + const char *acceptable_results1[] = {"0x1.0p+1", "0x2.0p+0", NULL}; + check_single_long_double_arr_allowed( + "%.1La", acceptable_results1, 1.999L); } -TEST(snprintf, apostropheFlag) { - char buf[20]; - int i = snprintf(buf, sizeof(buf), "%'d", 1000000); - ASSERT_EQ(7, i); - ASSERT_STREQ("1000000", buf); +TEST(snprintf, testApostropheFlag) { + check_single_int("%'d", "10000000", 10000000); +} + +TEST(snprintf, testUppercaseBConversionSpecifier) { + check_single_int("%B", "0", 0); + check_single_int("%B", "10", 2); + check_single_int("%#B", "0B10011", 19); } diff --git a/libc/stdio/srand.c b/test/libc/stdio/strfry_test.c similarity index 82% rename from libc/stdio/srand.c rename to test/libc/stdio/strfry_test.c index 8b072163e..a87ee54ba 100644 --- a/libc/stdio/srand.c +++ b/test/libc/stdio/strfry_test.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ Copyright 2024 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,12 +17,22 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/stdio/rand.h" +#include "libc/testlib/testlib.h" -extern uint64_t g_rando; - -/** - * Seeds random number generator that's used by rand(). - */ -void srand(unsigned seed) { - g_rando = seed; +TEST(strfry, empty) { + char s[1] = ""; + EXPECT_EQ(s, strfry(s)); + EXPECT_STREQ("", s); +} + +TEST(strfry, one) { + char s[2] = "a"; + EXPECT_EQ(s, strfry(s)); + EXPECT_STREQ("a", s); +} + +TEST(strfry, test) { + char s[5] = "abcd"; + EXPECT_EQ(s, strfry(s)); + EXPECT_STREQ("cbda", s); } diff --git a/test/libc/str/memccpy_test.c b/test/libc/str/memccpy_test.c index 5b54c189f..aebe301bb 100644 --- a/test/libc/str/memccpy_test.c +++ b/test/libc/str/memccpy_test.c @@ -16,10 +16,18 @@ │ 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/intrin/safemacros.h" #include "libc/mem/mem.h" +#include "libc/runtime/runtime.h" +#include "libc/runtime/sysconf.h" #include "libc/stdio/rand.h" #include "libc/stdio/stdio.h" #include "libc/str/str.h" +#include "libc/sysv/consts/map.h" +#include "libc/sysv/consts/prot.h" +#include "libc/testlib/benchmark.h" #include "libc/testlib/ezbench.h" #include "libc/testlib/testlib.h" @@ -50,6 +58,40 @@ TEST(memccpy, testZeroLength_doesNothing) { EXPECT_EQ(NULL, memccpy(buf, "hi", '\0', 0)); } +TEST(memccpy, fuzz) { + int pagesz = sysconf(_SC_PAGESIZE); + char *map1 = (char *)mmap(0, pagesz * 2, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + npassert(map1 != MAP_FAILED); + npassert(!mprotect(map1 + pagesz, pagesz, PROT_NONE)); + char *map2 = (char *)mmap(0, pagesz * 2, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + npassert(map2 != MAP_FAILED); + npassert(!mprotect(map2 + pagesz, pagesz, PROT_NONE)); + char *map3 = (char *)mmap(0, pagesz * 2, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + npassert(map3 != MAP_FAILED); + npassert(!mprotect(map3 + pagesz, pagesz, PROT_NONE)); + for (int dsize = 1; dsize < 128; ++dsize) { + char *volatile dst1 = map1 + pagesz - dsize; + char *volatile dst2 = map1 + pagesz - dsize; + for (int i = 0; i < dsize; ++i) + dst1[i] = dst2[i] = rand(); + for (int ssize = 1; ssize < dsize * 2; ++ssize) { + char *volatile src = map3 + pagesz - (ssize + 1); + for (int i = 0; i < ssize; ++i) + src[i] = max(rand() & 255, 1); + src[ssize] = 0; + ASSERT_EQ(memccpy_pure(dst1, src, 0, dsize), + memccpy(dst2, src, 0, dsize)); + ASSERT_EQ(0, memcmp(dst1, dst2, dsize)); + } + } + npassert(!munmap(map3, pagesz * 2)); + npassert(!munmap(map2, pagesz * 2)); + npassert(!munmap(map1, pagesz * 2)); +} + TEST(memccpy, memcpy) { unsigned n, n1, n2; char *b1, *b2, *b3, *e1, *e2; @@ -78,3 +120,26 @@ TEST(memccpy, memcpy) { free(b1); } } + +#define N 4096 + +BENCH(memccpy, bench) { + char dst[N]; + char src[N + 1]; + + printf("\n"); + for (int n = 1; n <= N; n *= 2) { + for (int i = 0; i < n; ++i) + src[i] = max(rand() & 255, 1); + src[n] = 0; + BENCHMARK(100, n, X(memccpy(dst, src, 0, V(N)))); + } + + printf("\n"); + for (int n = 1; n <= N; n *= 2) { + for (int i = 0; i < n; ++i) + src[i] = max(rand() & 255, 1); + src[n] = 0; + BENCHMARK(100, n, X(memccpy_pure(dst, src, 0, V(N)))); + } +} diff --git a/test/libc/str/strcasestr_test.c b/test/libc/str/strcasestr_test.c index 578e2e70e..cf012f866 100644 --- a/test/libc/str/strcasestr_test.c +++ b/test/libc/str/strcasestr_test.c @@ -17,12 +17,20 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/str/str.h" +#include "libc/assert.h" +#include "libc/calls/calls.h" #include "libc/dce.h" +#include "libc/intrin/safemacros.h" #include "libc/mem/alg.h" #include "libc/mem/gc.h" #include "libc/mem/mem.h" #include "libc/nexgen32e/x86feature.h" +#include "libc/runtime/runtime.h" +#include "libc/runtime/sysconf.h" +#include "libc/stdio/rand.h" #include "libc/str/tab.h" +#include "libc/sysv/consts/map.h" +#include "libc/sysv/consts/prot.h" #include "libc/testlib/ezbench.h" #include "libc/testlib/hyperion.h" #include "libc/testlib/testlib.h" @@ -49,6 +57,30 @@ char *strcasestr_naive(const char *haystack, const char *needle) { return 0; } +TEST(strcasestr, tester) { + const char *haystack = "Windows"; + ASSERT_STREQ(haystack, strcasestr(haystack, "win")); +} + +TEST(strcasestr, safety) { + int pagesz = sysconf(_SC_PAGESIZE); + char *map = (char *)mmap(0, pagesz * 2, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + npassert(map != MAP_FAILED); + npassert(!mprotect(map + pagesz, pagesz, PROT_NONE)); + for (int haylen = 1; haylen < 128; ++haylen) { + char *hay = map + pagesz - (haylen + 1); + for (int i = 0; i < haylen; ++i) + hay[i] = max(rand() & 255, 1); + hay[haylen] = 0; + for (int neelen = 1; neelen < haylen; ++neelen) { + char *nee = hay + (haylen + 1) - (neelen + 1); + ASSERT_EQ(strcasestr_naive(hay, nee), strcasestr(hay, nee)); + } + } + munmap(map, pagesz * 2); +} + TEST(strcasestr, test_emptyString_isFoundAtBeginning) { MAKESTRING(haystack, "abc123def"); ASSERT_STREQ(&haystack[0], strcasestr(haystack, gc(strdup("")))); diff --git a/test/libc/system/BUILD.mk b/test/libc/system/BUILD.mk new file mode 100644 index 000000000..ef1a6036d --- /dev/null +++ b/test/libc/system/BUILD.mk @@ -0,0 +1,116 @@ +#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐ +#── vi: set noet ft=make ts=8 sw=8 fenc=utf-8 :vi ────────────────────┘ + +PKGS += TEST_LIBC_SYSTEM + +TEST_LIBC_SYSTEM_FILES := $(wildcard test/libc/system/*) +TEST_LIBC_SYSTEM_SRCS = $(filter %.c,$(TEST_LIBC_SYSTEM_FILES)) +TEST_LIBC_SYSTEM_INCS = $(filter %.inc,$(TEST_LIBC_SYSTEM_FILES)) +TEST_LIBC_SYSTEM_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_SYSTEM_SRCS)) + +TEST_LIBC_SYSTEM_OBJS = \ + $(TEST_LIBC_SYSTEM_SRCS:%.c=o/$(MODE)/%.o) + +TEST_LIBC_SYSTEM_COMS = \ + $(TEST_LIBC_SYSTEM_SRCS:%.c=o/$(MODE)/%) + +TEST_LIBC_SYSTEM_BINS = \ + $(TEST_LIBC_SYSTEM_COMS) \ + $(TEST_LIBC_SYSTEM_COMS:%=%.dbg) + +TEST_LIBC_SYSTEM_TESTS = \ + $(TEST_LIBC_SYSTEM_SRCS_TEST:%.c=o/$(MODE)/%.ok) + +TEST_LIBC_SYSTEM_CHECKS = \ + $(TEST_LIBC_SYSTEM_SRCS_TEST:%.c=o/$(MODE)/%.runs) + +TEST_LIBC_SYSTEM_DIRECTDEPS = \ + LIBC_CALLS \ + LIBC_INTRIN \ + LIBC_LOG \ + LIBC_MEM \ + LIBC_NEXGEN32E \ + LIBC_PROC \ + LIBC_RUNTIME \ + LIBC_STDIO \ + LIBC_STR \ + LIBC_SYSTEM \ + LIBC_SYSV \ + LIBC_TESTLIB \ + LIBC_THREAD \ + LIBC_X \ + THIRD_PARTY_MUSL \ + THIRD_PARTY_TR \ + THIRD_PARTY_TZ \ + +TEST_LIBC_SYSTEM_DEPS := \ + $(call uniq,$(foreach x,$(TEST_LIBC_SYSTEM_DIRECTDEPS),$($(x)))) + +o/$(MODE)/test/libc/system/system.pkg: \ + $(TEST_LIBC_SYSTEM_OBJS) \ + $(foreach x,$(TEST_LIBC_SYSTEM_DIRECTDEPS),$($(x)_A).pkg) + +o/$(MODE)/test/libc/system/%.dbg: \ + $(TEST_LIBC_SYSTEM_DEPS) \ + o/$(MODE)/test/libc/system/%.o \ + o/$(MODE)/test/libc/system/system.pkg \ + o/$(MODE)/tool/build/echo.zip.o \ + $(LIBC_TESTMAIN) \ + $(CRT) \ + $(APE_NO_MODIFY_SELF) + @$(APELINK) + +o/$(MODE)/test/libc/system/popen_test.dbg: \ + $(TEST_LIBC_SYSTEM_DEPS) \ + o/$(MODE)/test/libc/system/popen_test.o \ + o/$(MODE)/test/libc/system/system.pkg \ + o/$(MODE)/tool/build/echo.zip.o \ + $(LIBC_TESTMAIN) \ + $(CRT) \ + $(APE_NO_MODIFY_SELF) + @$(APELINK) + +o/$(MODE)/test/libc/system/system_test.dbg: \ + $(TEST_LIBC_SYSTEM_DEPS) \ + o/$(MODE)/test/libc/system/system_test.o \ + o/$(MODE)/test/libc/system/system.pkg \ + o/$(MODE)/tool/build/echo.zip.o \ + o/$(MODE)/tool/build/cocmd.zip.o \ + o/$(MODE)/tool/build/false.zip.o \ + $(LIBC_TESTMAIN) \ + $(CRT) \ + $(APE_NO_MODIFY_SELF) + @$(APELINK) + +o/$(MODE)/test/libc/system/trace_test.dbg: \ + $(TEST_LIBC_SYSTEM_DEPS) \ + o/$(MODE)/test/libc/system/trace_test.o \ + o/$(MODE)/test/libc/system/system.pkg \ + o/$(MODE)/test/libc/system/popen_test.zip.o \ + o/$(MODE)/test/libc/system/popen_test.dbg.zip.o \ + o/$(MODE)/tool/build/echo.zip.o \ + $(LIBC_TESTMAIN) \ + $(CRT) \ + $(APE_NO_MODIFY_SELF) + @$(APELINK) + +o/$(MODE)/test/libc/system/systemvpe_test.dbg: \ + $(TEST_LIBC_SYSTEM_DEPS) \ + o/$(MODE)/test/libc/system/systemvpe_test.o \ + o/$(MODE)/test/libc/system/system.pkg \ + o/$(MODE)/test/libc/proc/life.zip.o \ + $(LIBC_TESTMAIN) \ + $(CRT) \ + $(APE_NO_MODIFY_SELF) + @$(APELINK) + + +o/$(MODE)/test/libc/system/popen_test.zip.o: private ZIPOBJ_FLAGS += -B +o/$(MODE)/test/libc/system/popen_test.dbg.zip.o: private ZIPOBJ_FLAGS += -B + +$(TEST_LIBC_SYSTEM_OBJS): test/libc/system/BUILD.mk + +.PHONY: o/$(MODE)/test/libc/system +o/$(MODE)/test/libc/system: \ + $(TEST_LIBC_SYSTEM_BINS) \ + $(TEST_LIBC_SYSTEM_CHECKS) diff --git a/test/libc/stdio/popen_test.c b/test/libc/system/popen_test.c similarity index 86% rename from test/libc/stdio/popen_test.c rename to test/libc/system/popen_test.c index 648e885ac..b0099bdc7 100644 --- a/test/libc/stdio/popen_test.c +++ b/test/libc/system/popen_test.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/struct/itimerval.h" #include "libc/calls/struct/sigaction.h" #include "libc/dce.h" #include "libc/errno.h" @@ -31,16 +32,54 @@ #include "libc/stdio/stdio.h" #include "libc/str/str.h" #include "libc/sysv/consts/f.h" +#include "libc/sysv/consts/itimer.h" #include "libc/sysv/consts/sig.h" #include "libc/testlib/testlib.h" #include "libc/thread/thread.h" -#ifdef __x86_64__ +#include "libc/time.h" + +// test ability of user to override pthread mutex api +int pthread_mutex_lock(pthread_mutex_t *mutex) { + abort(); +} +int pthread_mutex_unlock(pthread_mutex_t *mutex) { + abort(); +} +int pthread_mutex_trylock(pthread_mutex_t *mutex) { + abort(); +} +int pthread_mutex_wipe_np(pthread_mutex_t *mutex) { + abort(); +} FILE *f; char buf[32]; +void OnAlarm(int sig) { +} + +void *LolThread(void *arg) { + return 0; +} + void SetUpOnce(void) { testlib_enable_tmp_setup_teardown(); + + // give deadlock detector more information + int64_t t = 0x5cd04d0e; + localtime(&t); + pthread_t th; + pthread_create(&th, 0, LolThread, 0); + pthread_join(th, 0); + char buf[32]; + sprintf(buf, "%g", 3.14); + atexit((void *)LolThread); + FILE *f = fopen("/zip/.cosmo", "r"); + fgetc(f); + fclose(f); + signal(SIGALRM, OnAlarm); + struct itimerval it = {{0, 1000}, {0, 1}}; + setitimer(ITIMER_REAL, &it, 0); } void CheckForFdLeaks(void) { @@ -120,8 +159,6 @@ void OnSig(int sig) { } TEST(popen, complicated) { - if (IsWindows()) - return; // windows treats sigusr1 as terminate char cmd[64]; signal(SIGUSR1, OnSig); sprintf(cmd, "read a ; test \"x$a\" = xhello && kill -USR1 %d", getpid()); @@ -171,5 +208,3 @@ TEST(popen, torture) { ASSERT_EQ(0, pthread_join(t[i], 0)); CheckForFdLeaks(); } - -#endif /* __x86_64__ */ diff --git a/test/libc/proc/system_test.c b/test/libc/system/system_test.c similarity index 94% rename from test/libc/proc/system_test.c rename to test/libc/system/system_test.c index b80873320..3773a64e0 100644 --- a/test/libc/proc/system_test.c +++ b/test/libc/system/system_test.c @@ -27,10 +27,9 @@ #include "libc/str/str.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/sig.h" -#include "libc/testlib/ezbench.h" +#include "libc/testlib/benchmark.h" #include "libc/testlib/testlib.h" #include "libc/x/x.h" -#ifdef __x86_64__ #define GETEXITSTATUS(x) \ ({ \ @@ -166,13 +165,12 @@ TEST(system, notequals) { } TEST(system, usleep) { - ASSERT_EQ(0, GETEXITSTATUS(system("usleep & kill $!"))); + ASSERT_EQ(0, GETEXITSTATUS(system("usleep & kill $!; wait"))); } TEST(system, kill) { int ws = system("kill -TERM $$; usleep"); - if (!IsWindows()) - ASSERT_EQ(SIGTERM, WTERMSIG(ws)); + ASSERT_EQ(SIGTERM, WTERMSIG(ws)); } TEST(system, exitStatusPreservedAfterSemiColon) { @@ -277,15 +275,9 @@ TEST(system, pipelineCanOutputBackToSelf) { RestoreStdout(); } -int system2(const char *); - -BENCH(system, bench) { +TEST(system, bench) { testlib_extract("/zip/echo", "echo", 0755); - EZBENCH2("system cmd", donothing, system("./echo hi >/dev/null")); - EZBENCH2("systemvpe cmd", donothing, - systemvpe("./echo", (char *[]){"./echo", "hi", 0}, 0)); - EZBENCH2("cocmd echo", donothing, system("echo hi >/dev/null")); - EZBENCH2("cocmd exit", donothing, system("exit")); + BENCHMARK(10, 1, system("./echo hi >/dev/null")); + BENCHMARK(10, 1, system("echo hi >/dev/null")); + BENCHMARK(10, 1, system("exit")); } - -#endif /* __x86_64__ */ diff --git a/test/libc/calls/fchmod_test.c b/test/libc/system/systemvpe_test.c similarity index 79% rename from test/libc/calls/fchmod_test.c rename to test/libc/system/systemvpe_test.c index df8ffcc2b..bcbc5a30c 100644 --- a/test/libc/calls/fchmod_test.c +++ b/test/libc/system/systemvpe_test.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2023 Justine Alexandra Roberts Tunney │ +│ Copyright 2024 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,25 +17,18 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" -#include "libc/calls/struct/stat.h" +#include "libc/cosmo.h" +#include "libc/runtime/runtime.h" #include "libc/testlib/testlib.h" void SetUpOnce(void) { testlib_enable_tmp_setup_teardown(); } -uint32_t GetMode(int fd) { - struct stat st; - ASSERT_SYS(0, 0, fstat(fd, &st)); - return st.st_mode & 0777; -} - -TEST(fchmod, canChangeReadOnlyBit) { - ASSERT_SYS(0, 3, creat("foo", 0600)); - ASSERT_EQ(0600, GetMode(3)); - ASSERT_SYS(0, 0, fchmod(3, 0400)); - ASSERT_EQ(0400, GetMode(3)); - ASSERT_SYS(0, 0, fchmod(3, 0600)); - ASSERT_EQ(0600, GetMode(3)); - ASSERT_SYS(0, 0, close(3)); +TEST(systemvpe, test) { + ASSERT_SYS(0, 0, mkdir("bin", 0755)); + ASSERT_SYS(0, 0, setenv("PATH", "bin", true)); + testlib_extract("/zip/life", "bin/life", 0755); + ASSERT_SYS(0, 42 << 8, + systemvpe("life", (char *[]){"life", 0}, (char *[]){0})); } diff --git a/test/libc/system/trace_test.c b/test/libc/system/trace_test.c new file mode 100644 index 000000000..7a661a2fc --- /dev/null +++ b/test/libc/system/trace_test.c @@ -0,0 +1,74 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 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/calls.h" +#include "libc/mem/mem.h" +#include "libc/runtime/runtime.h" +#include "libc/stdio/stdio.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/o.h" +#include "libc/testlib/testlib.h" +#include "libc/x/x.h" + +// make sure that running `popen_test --ftrace --strace` doesn't crash +// +// function and system call tracing are invasive runtime features that +// can easily break if interrupting the other magical, deeply embedded +// parts of the runtime, like mutations to the rbtree ftrace needs for +// validating stack pointers (kisdangerous() locks the mmap lock), and +// that's why we use dontinstrument in so many places in the codebase. +// +// we like popen_test because it tests the intersection of forking and +// threads, and it activates other subsystems like the signal / itimer +// worker threads on windows. if we can ftrace and strace it, then you +// can be assured cosmo's tracing support works right on all platforms + +void SetUpOnce(void) { + testlib_enable_tmp_setup_teardown(); +} + +TEST(trace, test) { + unsetenv("MAKEFLAGS"); // avoid testmain.c 254 status + testlib_extract("/zip/popen_test", "popen_test", 0755); + testlib_extract("/zip/popen_test.dbg", "popen_test.dbg", 0755); + if (!fork()) { + close(1); + close(2); + open("log", O_CREAT | O_TRUNC | O_WRONLY | O_APPEND, 0644); + dup(1); + execl("./popen_test", "./popen_test", "--ftrace", "--strace", NULL); + _Exit(128); + } + int ws; + unassert(wait(&ws)); + if (WIFSIGNALED(ws)) { + fprintf(stderr, + "%s:%d: error: trace_test got %s signal running " + "popen_test --strace --ftrace (see %s for output)\n", + __FILE__, __LINE__, strsignal(WTERMSIG(ws)), realpath("log", 0)); + _Exit(1); + } + if (WEXITSTATUS(ws)) { + fprintf(stderr, + "%s:%d: error: trace_test got %d exit status running " + "popen_test --strace --ftrace (see %s for output)\n", + __FILE__, __LINE__, WEXITSTATUS(ws), realpath("log", 0)); + _Exit(1); + } +} diff --git a/test/libc/thread/BUILD.mk b/test/libc/thread/BUILD.mk index 78e185361..1ac2769be 100644 --- a/test/libc/thread/BUILD.mk +++ b/test/libc/thread/BUILD.mk @@ -41,6 +41,7 @@ TEST_LIBC_THREAD_DIRECTDEPS = \ LIBC_SOCK \ LIBC_STDIO \ LIBC_STR \ + LIBC_SYSTEM \ LIBC_SYSV \ LIBC_SYSV_CALLS \ LIBC_TESTLIB \ @@ -49,7 +50,7 @@ TEST_LIBC_THREAD_DIRECTDEPS = \ THIRD_PARTY_LIBCXXABI \ THIRD_PARTY_NSYNC \ THIRD_PARTY_NSYNC_MEM \ - THIRD_PARTY_TZ + THIRD_PARTY_TZ \ TEST_LIBC_THREAD_DEPS := \ $(call uniq,$(foreach x,$(TEST_LIBC_THREAD_DIRECTDEPS),$($(x)))) diff --git a/test/libc/thread/footek_test.c b/test/libc/thread/footek_test.c index 51d3bd1c9..b08846ae3 100644 --- a/test/libc/thread/footek_test.c +++ b/test/libc/thread/footek_test.c @@ -1,16 +1,17 @@ -#define USE POSIX_RECURSIVE +#define USE POSIX #define ITERATIONS 100000 #define THREADS 30 #define SPIN 1 #define FUTEX 2 -#define POSIX 3 -#define POSIX_RECURSIVE 4 +#define FUTEX_SHARED 3 +#define POSIX 4 +#define POSIX_RECURSIVE 5 +#define RWLOCK 6 +#define RWLOCK_SHARED 7 #ifdef __COSMOPOLITAN__ #include -#include "libc/thread/thread.h" -#include "third_party/nsync/futex.internal.h" #endif #include @@ -276,8 +277,11 @@ void lock(atomic_int *futex) { word = atomic_exchange_explicit(futex, 2, memory_order_acquire); while (word > 0) { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs); -#if USE == FUTEX - nsync_futex_wait_(futex, 2, 0, 0, 0); +#if USE == FUTEX || USE == FUTEX_SHARED + cosmo_futex_wait( + futex, 2, + USE == FUTEX_SHARED ? PTHREAD_PROCESS_SHARED : PTHREAD_PROCESS_PRIVATE, + 0, 0); #else pthread_yield_np(); #endif @@ -290,8 +294,10 @@ void unlock(atomic_int *futex) { int word = atomic_fetch_sub_explicit(futex, 1, memory_order_release); if (word == 2) { atomic_store_explicit(futex, 0, memory_order_release); -#if USE == FUTEX - nsync_futex_wake_(futex, 1, 0); +#if USE == FUTEX || USE == FUTEX_SHARED + cosmo_futex_wake( + futex, 1, + USE == FUTEX_SHARED ? PTHREAD_PROCESS_SHARED : PTHREAD_PROCESS_PRIVATE); #endif } } @@ -299,6 +305,7 @@ void unlock(atomic_int *futex) { int g_chores; atomic_int g_lock; pthread_mutex_t g_locker; +pthread_rwlock_t g_rwlocker; void *worker(void *arg) { for (int i = 0; i < ITERATIONS; ++i) { @@ -306,6 +313,10 @@ void *worker(void *arg) { pthread_mutex_lock(&g_locker); ++g_chores; pthread_mutex_unlock(&g_locker); +#elif USE == RWLOCK || USE == RWLOCK_SHARED + pthread_rwlock_wrlock(&g_rwlocker); + ++g_chores; + pthread_rwlock_unlock(&g_rwlocker); #else lock(&g_lock); ++g_chores; @@ -333,17 +344,24 @@ int main() { struct timeval start; gettimeofday(&start, 0); - pthread_mutex_t lock; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); #if USE == POSIX_RECURSIVE pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); #else - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); #endif pthread_mutex_init(&g_locker, &attr); pthread_mutexattr_destroy(&attr); + pthread_rwlockattr_t rwattr; + pthread_rwlockattr_init(&rwattr); +#if USE == RWLOCK_SHARED + pthread_rwlockattr_setpshared(&rwattr, PTHREAD_PROCESS_SHARED); +#endif + pthread_rwlock_init(&g_rwlocker, &rwattr); + pthread_rwlockattr_destroy(&rwattr); + pthread_t th[THREADS]; for (int i = 0; i < THREADS; ++i) pthread_create(&th[i], 0, worker, 0); @@ -362,7 +380,8 @@ int main() { tomicros(ru.ru_utime), // tomicros(ru.ru_stime)); - pthread_mutex_destroy(&lock); + pthread_rwlock_destroy(&g_rwlocker); + pthread_mutex_destroy(&g_locker); #ifdef __COSMOPOLITAN__ CheckForMemoryLeaks(); diff --git a/test/libc/thread/pthread_atfork_test.c b/test/libc/thread/pthread_atfork_test.c index ba3c9b056..8a6d5d4d0 100644 --- a/test/libc/thread/pthread_atfork_test.c +++ b/test/libc/thread/pthread_atfork_test.c @@ -22,6 +22,7 @@ #include "libc/intrin/atomic.h" #include "libc/intrin/kprintf.h" #include "libc/mem/gc.h" +#include "libc/mem/leaks.h" #include "libc/mem/mem.h" #include "libc/runtime/internal.h" #include "libc/runtime/runtime.h" @@ -51,7 +52,6 @@ TEST(pthread_atfork, test) { SPAWN(fork); ASSERT_EQ(0, pthread_atfork(prepare1, parent1, child1)); ASSERT_EQ(0, pthread_atfork(prepare2, parent2, child2)); - flockfile(stdout); SPAWN(fork); flockfile(stdout); ASSERT_STREQ("prepare2", A[0]); @@ -60,7 +60,6 @@ TEST(pthread_atfork, test) { ASSERT_STREQ("child2", A[3]); funlockfile(stdout); EXITS(0); - funlockfile(stdout); ASSERT_STREQ("prepare2", A[0]); ASSERT_STREQ("prepare1", A[1]); ASSERT_STREQ("parent1", A[2]); @@ -79,7 +78,7 @@ void mu_unlock(void) { } void mu_wipe(void) { - pthread_mutex_init(&mu, 0); + pthread_mutex_wipe_np(&mu); } void *Worker(void *arg) { diff --git a/test/libc/thread/pthread_cancel_deferred_cond_test.c b/test/libc/thread/pthread_cancel_deferred_cond_test.c index 76d9eb928..4bba81a18 100644 --- a/test/libc/thread/pthread_cancel_deferred_cond_test.c +++ b/test/libc/thread/pthread_cancel_deferred_cond_test.c @@ -1,8 +1,23 @@ +// Copyright 2024 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 #include +#include #include #include -#include "libc/stdio/stdio.h" int got_cleanup; pthread_cond_t cv; @@ -26,7 +41,7 @@ int main(int argc, char* argv[]) { pthread_t th; pthread_mutexattr_t at; pthread_mutexattr_init(&at); - pthread_mutexattr_settype(&at, PTHREAD_MUTEX_NORMAL); + pthread_mutexattr_settype(&at, PTHREAD_MUTEX_DEFAULT); pthread_mutex_init(&mu, &at); pthread_mutexattr_destroy(&at); pthread_cond_init(&cv, 0); @@ -42,8 +57,6 @@ int main(int argc, char* argv[]) { return 6; if (pthread_mutex_trylock(&mu) != EBUSY) return 7; - if (pthread_mutex_unlock(&mu)) - return 8; pthread_mutex_destroy(&mu); pthread_cond_destroy(&cv); } diff --git a/test/libc/thread/pthread_cancel_test.c b/test/libc/thread/pthread_cancel_test.c index 56f9fa5d5..06fb5093e 100644 --- a/test/libc/thread/pthread_cancel_test.c +++ b/test/libc/thread/pthread_cancel_test.c @@ -19,6 +19,7 @@ #include "libc/atomic.h" #include "libc/calls/calls.h" #include "libc/calls/struct/sigaction.h" +#include "libc/calls/struct/sigaltstack.h" #include "libc/dce.h" #include "libc/errno.h" #include "libc/intrin/kprintf.h" @@ -27,6 +28,7 @@ #include "libc/nexgen32e/nexgen32e.h" #include "libc/runtime/internal.h" #include "libc/runtime/runtime.h" +#include "libc/runtime/sysconf.h" #include "libc/sysv/consts/sig.h" #include "libc/testlib/testlib.h" #include "libc/thread/thread.h" @@ -40,11 +42,7 @@ atomic_int gotcleanup; void SetUpOnce(void) { testlib_enable_tmp_setup_teardown(); - pthread_mutexattr_t at; - pthread_mutexattr_init(&at); - pthread_mutexattr_settype(&at, PTHREAD_MUTEX_NORMAL); - pthread_mutex_init(&mu, &at); - pthread_mutexattr_destroy(&at); + pthread_mutex_init(&mu, 0); pthread_cond_init(&cv, 0); } @@ -194,6 +192,7 @@ TEST(pthread_cancel, condDeferredWait_reacquiresMutex) { ASSERT_EQ(0, pthread_join(th, &rc)); ASSERT_EQ(PTHREAD_CANCELED, rc); ASSERT_EQ(EBUSY, pthread_mutex_trylock(&mu)); + ASSERT_EQ(0, pthread_mutex_consistent(&mu)); ASSERT_EQ(0, pthread_mutex_unlock(&mu)); } @@ -206,6 +205,7 @@ TEST(pthread_cancel, condDeferredWaitDelayed) { ASSERT_EQ(0, pthread_join(th, &rc)); ASSERT_EQ(PTHREAD_CANCELED, rc); ASSERT_EQ(EBUSY, pthread_mutex_trylock(&mu)); + ASSERT_EQ(0, pthread_mutex_consistent(&mu)); ASSERT_EQ(0, pthread_mutex_unlock(&mu)); } diff --git a/test/libc/thread/pthread_create_test.c b/test/libc/thread/pthread_create_test.c index 03465ff93..92b6c28db 100644 --- a/test/libc/thread/pthread_create_test.c +++ b/test/libc/thread/pthread_create_test.c @@ -22,6 +22,8 @@ #include "libc/calls/struct/sched_param.h" #include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/siginfo.h" +#include "libc/calls/struct/sigset.h" +#include "libc/cosmo.h" #include "libc/dce.h" #include "libc/errno.h" #include "libc/intrin/kprintf.h" @@ -40,16 +42,36 @@ #include "libc/sysv/consts/sched.h" #include "libc/sysv/consts/sig.h" #include "libc/sysv/consts/ss.h" +#include "libc/testlib/benchmark.h" #include "libc/testlib/ezbench.h" +#include "libc/testlib/manystack.h" #include "libc/testlib/subprocess.h" #include "libc/testlib/testlib.h" #include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" #include "libc/thread/thread2.h" +// test ability of user to override pthread mutex api +int pthread_mutex_lock(pthread_mutex_t *mutex) { + abort(); +} +int pthread_mutex_unlock(pthread_mutex_t *mutex) { + abort(); +} +int pthread_mutex_trylock(pthread_mutex_t *mutex) { + abort(); +} +int pthread_mutex_wipe_np(pthread_mutex_t *mutex) { + abort(); +} + void OnUsr1(int sig, siginfo_t *si, void *vctx) { } +void SetUpOnce(void) { + cosmo_stack_setmaxstacks((_rand64() & 7) - 1); +} + void SetUp(void) { struct sigaction sig = {.sa_sigaction = OnUsr1, .sa_flags = SA_SIGINFO}; sigaction(SIGUSR1, &sig, 0); @@ -280,10 +302,60 @@ static void CreateDetached(void) { ASSERT_EQ(0, pthread_attr_destroy(&attr)); } +#define LAUNCHES 10 +#define LAUNCHERS 10 + +errno_t pthread_create2(pthread_t *thread, const pthread_attr_t *attr, + void *(*start_routine)(void *), void *arg) { + for (int i = 1;; i <<= 1) { + errno_t err = pthread_create(thread, attr, start_routine, arg); + if (err != EAGAIN) + return err; + usleep(i); + } +} + +static void *CreateDetachedParallelThreads(void *arg) { + for (int i = 0; i < LAUNCHES; ++i) + CreateDetached(); + return 0; +} + +static void CreateDetachedParallel(void) { + pthread_t th[LAUNCHERS]; + for (int i = 0; i < LAUNCHERS; ++i) + ASSERT_EQ(0, pthread_create2(&th[i], 0, CreateDetachedParallelThreads, 0)); + for (int i = 0; i < LAUNCHERS; ++i) + ASSERT_EQ(0, pthread_join(th[i], 0)); +} + +static void *CreateJoinParallelThreads(void *arg) { + for (int i = 0; i < LAUNCHES; ++i) + CreateJoin(); + return 0; +} + +static void CreateJoinParallel(void) { + pthread_t th[LAUNCHERS]; + for (int i = 0; i < LAUNCHERS; ++i) + ASSERT_EQ(0, pthread_create2(&th[i], 0, CreateJoinParallelThreads, 0)); + for (int i = 0; i < LAUNCHERS; ++i) + ASSERT_EQ(0, pthread_join(th[i], 0)); +} + TEST(pthread_create, bench) { - EZBENCH2("CreateJoin", donothing, CreateJoin()); - EZBENCH2("CreateDetach", donothing, CreateDetach()); - EZBENCH2("CreateDetached", donothing, CreateDetached()); + kprintf("cosmo_stack_getmaxstacks() = %d\n", cosmo_stack_getmaxstacks()); + pthread_t msh = manystack_start(); + BENCHMARK(100, 1, CreateJoin()); + BENCHMARK(100, 1, CreateDetach()); + usleep(10000); + pthread_decimate_np(); + BENCHMARK(100, 1, CreateDetached()); + usleep(10000); + pthread_decimate_np(); + BENCHMARK(1, LAUNCHERS + LAUNCHERS * LAUNCHES, CreateJoinParallel()); + BENCHMARK(1, LAUNCHERS + LAUNCHERS * LAUNCHES, CreateDetachedParallel()); + manystack_stop(msh); while (!pthread_orphan_np()) pthread_decimate_np(); } diff --git a/test/libc/thread/pthread_kill_test.c b/test/libc/thread/pthread_kill_test.c index f6494b137..2ac31f4be 100644 --- a/test/libc/thread/pthread_kill_test.c +++ b/test/libc/thread/pthread_kill_test.c @@ -259,7 +259,6 @@ void *CpuWorker(void *arg) { } TEST(pthread_kill, canAsynchronouslyRunHandlerInsideTargetThread) { - ASSERT_NE(0, __get_tls()->tib_tid); pthread_t t; struct sigaction oldsa; struct sigaction sa = {.sa_handler = OnSigAsync}; @@ -273,7 +272,6 @@ TEST(pthread_kill, canAsynchronouslyRunHandlerInsideTargetThread) { ASSERT_TRUE(exited_original_loop); ASSERT_SYS(0, 0, sigaction(SIGUSR1, &oldsa, 0)); ASSERT_EQ(0, gotsig); - ASSERT_NE(0, __get_tls()->tib_tid); } volatile int is_having_fun; @@ -287,7 +285,6 @@ void *FunWorker(void *arg) { } TEST(pthread_kill, defaultThreadSignalHandlerWillKillWholeProcess) { - ASSERT_NE(0, __get_tls()->tib_tid); SPAWN(fork); pthread_t t; ASSERT_EQ(0, pthread_create(&t, 0, FunWorker, 0)); @@ -297,7 +294,6 @@ TEST(pthread_kill, defaultThreadSignalHandlerWillKillWholeProcess) { for (;;) sched_yield(); TERMS(SIGKILL); - ASSERT_NE(0, __get_tls()->tib_tid); } void *SuspendWorker(void *arg) { @@ -308,7 +304,6 @@ void *SuspendWorker(void *arg) { } TEST(pthread_kill, canInterruptSigsuspend) { - ASSERT_NE(0, __get_tls()->tib_tid); int tid; void *res; pthread_t t; diff --git a/test/libc/thread/pthread_rwlock_rdlock_test.c b/test/libc/thread/pthread_rwlock_rdlock_test.c index e7ad11cc3..4fba1f503 100644 --- a/test/libc/thread/pthread_rwlock_rdlock_test.c +++ b/test/libc/thread/pthread_rwlock_rdlock_test.c @@ -17,26 +17,55 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/atomic.h" +#include "libc/calls/calls.h" +#include "libc/intrin/atomic.h" #include "libc/mem/gc.h" #include "libc/mem/mem.h" +#include "libc/stdalign.h" +#include "libc/stdio/rand.h" #include "libc/testlib/testlib.h" #include "libc/thread/thread.h" -#define ITERATIONS 50000 #define READERS 8 #define WRITERS 2 +#define ITERATIONS 1000 -atomic_int reads; -atomic_int writes; +atomic_bool done; +alignas(128) int foo; +alignas(128) int bar; pthread_rwlock_t lock; +pthread_rwlockattr_t attr; pthread_barrier_t barrier; +void delay(int k) { + int n = rand() % k; + for (volatile int i = 0; i < n; ++i) { + } +} + +void SetUp(void) { + ASSERT_EQ(0, pthread_rwlockattr_init(&attr)); + ASSERT_EQ(0, pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED)); + ASSERT_EQ(0, pthread_rwlock_init(&lock, &attr)); + ASSERT_EQ(0, pthread_rwlockattr_destroy(&attr)); +} + +void TearDown(void) { + ASSERT_EQ(0, pthread_rwlock_destroy(&lock)); +} + void *Reader(void *arg) { pthread_barrier_wait(&barrier); - for (int i = 0; i < ITERATIONS; ++i) { + while (!atomic_load_explicit(&done, memory_order_relaxed)) { ASSERT_EQ(0, pthread_rwlock_rdlock(&lock)); - ++reads; + // cosmo_trace_begin("reader"); + int x = foo; + usleep(1); // delay(100000); + int y = bar; + ASSERT_EQ(x, y); + // cosmo_trace_end("reader"); ASSERT_EQ(0, pthread_rwlock_unlock(&lock)); + usleep(1); // delay(100000); } return 0; } @@ -45,9 +74,15 @@ void *Writer(void *arg) { pthread_barrier_wait(&barrier); for (int i = 0; i < ITERATIONS; ++i) { ASSERT_EQ(0, pthread_rwlock_wrlock(&lock)); - ++writes; + // cosmo_trace_begin("writer"); + ++foo; + delay(10); + ++bar; + // cosmo_trace_end("writer"); ASSERT_EQ(0, pthread_rwlock_unlock(&lock)); + delay(10); } + done = true; return 0; } @@ -55,14 +90,12 @@ TEST(pthread_rwlock_rdlock, test) { int i; pthread_t *t = gc(malloc(sizeof(pthread_t) * (READERS + WRITERS))); ASSERT_EQ(0, pthread_barrier_init(&barrier, 0, READERS + WRITERS)); - for (i = 0; i < READERS + WRITERS; ++i) { + for (i = 0; i < READERS + WRITERS; ++i) ASSERT_SYS(0, 0, pthread_create(t + i, 0, i < READERS ? Reader : Writer, 0)); - } - for (i = 0; i < READERS + WRITERS; ++i) { + for (i = 0; i < READERS + WRITERS; ++i) EXPECT_SYS(0, 0, pthread_join(t[i], 0)); - } - EXPECT_EQ(READERS * ITERATIONS, reads); - EXPECT_EQ(WRITERS * ITERATIONS, writes); + EXPECT_EQ(WRITERS * ITERATIONS, foo); + EXPECT_EQ(WRITERS * ITERATIONS, bar); ASSERT_EQ(0, pthread_barrier_destroy(&barrier)); } diff --git a/test/libc/intrin/pthread_spin_lock_test.c b/test/libc/thread/pthread_spin_lock_test.c similarity index 100% rename from test/libc/intrin/pthread_spin_lock_test.c rename to test/libc/thread/pthread_spin_lock_test.c diff --git a/test/libc/thread/setitimer_test.c b/test/libc/thread/setitimer_test.c index d63c65e5f..061faf459 100644 --- a/test/libc/thread/setitimer_test.c +++ b/test/libc/thread/setitimer_test.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/sysv/consts/itimer.h" #include "libc/atomic.h" #include "libc/calls/calls.h" #include "libc/calls/struct/itimerval.h" @@ -28,7 +29,6 @@ #include "libc/errno.h" #include "libc/limits.h" #include "libc/runtime/runtime.h" -#include "libc/sysv/consts/itimer.h" #include "libc/sysv/consts/sa.h" #include "libc/sysv/consts/sicode.h" #include "libc/sysv/consts/sig.h" diff --git a/test/libc/x/utf8to32_test.c b/test/libc/x/utf8to32_test.c index cf17662bb..5f54e2d17 100644 --- a/test/libc/x/utf8to32_test.c +++ b/test/libc/x/utf8to32_test.c @@ -18,7 +18,6 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/mem/gc.h" #include "libc/mem/mem.h" -#include "libc/mem/shuffle.internal.h" #include "libc/stdio/rand.h" #include "libc/testlib/ezbench.h" #include "libc/testlib/hyperion.h" @@ -82,11 +81,20 @@ TEST(utf32to8, testLargeThompsonPikeEncoded) { -1, 0))); } +void shuffle(wchar_t *a, int n) { + for (int i = n - 1; i >= 1; --i) { + int r = rand() % (i + 1); + wchar_t t = a[r]; + a[r] = a[i]; + a[i] = t; + } +} + char *GenerateBranchyUtf8Text(size_t *out_n) { char *p; size_t n; wchar_t *q = gc(utf8to32(kViewables, kViewablesSize, &n)); - shuffle(lemur64, q, n); + shuffle(q, n); p = utf32to8(q, n, &n); if (out_n) *out_n = n; diff --git a/test/net/http/parsehttpmessage_test.c b/test/net/http/parsehttpmessage_test.c index 5a8412614..9b63cd629 100644 --- a/test/net/http/parsehttpmessage_test.c +++ b/test/net/http/parsehttpmessage_test.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/errno.h" +#include "libc/intrin/kprintf.h" #include "libc/log/check.h" #include "libc/mem/gc.h" #include "libc/mem/mem.h" @@ -434,6 +435,24 @@ TEST(ParseHttpResponse, testHttp100) { EXPECT_EQ(10, req->version); } +TEST(ParseHttpMessage, issue1315) { + static const char m[] = "\ +HTTP/1.1 200 OK\r\n\ +padding: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n\ +Date: Sat, 12 Oct 2024 22:50:55 GMT\r\n\ +Server: redbean/3.0.0\r\n\ +Connection: close\r\n\ +Content-Type: text/html; charset=utf-8\r\n\ +Referrer-Policy: no-referrer-when-downgrade\r\n\ +Content-Length: 12\r\n\ +\r\n"; + InitHttpMessage(req, kHttpResponse); + EXPECT_EQ(0, ParseHttpMessage(req, m, strlen(m) - 1, strlen(m))); + EXPECT_EQ(200, req->status); +} + +//////////////////////////////////////////////////////////////////////////////// + void DoTiniestHttpRequest(void) { static const char m[] = "\ GET /\r\n\ diff --git a/test/posix/cyclic_mutex_test.c b/test/posix/cyclic_mutex_test.c new file mode 100644 index 000000000..28c733751 --- /dev/null +++ b/test/posix/cyclic_mutex_test.c @@ -0,0 +1,71 @@ +// Copyright 2024 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 +#include +#include +#include + +pthread_mutex_t x; +pthread_mutex_t y; + +void ignore_signal(int sig) { +} + +int main(int argc, char *argv[]) { + +#ifdef MODE_DBG + GetSymbolTable(); + signal(SIGTRAP, ignore_signal); + kprintf("running %s\n", argv[0]); +#endif + + pthread_mutexattr_t attr; + if (pthread_mutexattr_init(&attr)) + return 1; + if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK)) + return 2; + if (pthread_mutex_init(&x, &attr)) + return 3; + if (pthread_mutex_init(&y, &attr)) + return 4; + if (pthread_mutexattr_destroy(&attr)) + return 5; + + if (pthread_mutex_lock(&x)) + return 6; + if (pthread_mutex_lock(&y)) + return 7; + if (pthread_mutex_unlock(&y)) + return 8; + if (pthread_mutex_unlock(&x)) + return 9; + + if (pthread_mutex_lock(&y)) + return 10; + if (pthread_mutex_lock(&y) != EDEADLK) + return 11; + if (pthread_mutex_lock(&x) != EDEADLK) + return 12; + if (pthread_mutex_unlock(&x) != EPERM) + return 13; + if (pthread_mutex_unlock(&y)) + return 14; + + if (pthread_mutex_destroy(&y)) + return 15; + if (pthread_mutex_destroy(&x)) + return 16; +} diff --git a/test/posix/file_offset_exec_test.c b/test/posix/file_offset_exec_test.c index aafc9061a..e9b9e94ba 100644 --- a/test/posix/file_offset_exec_test.c +++ b/test/posix/file_offset_exec_test.c @@ -13,6 +13,7 @@ // TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. +#include #include #include #include @@ -36,6 +37,7 @@ void on_unexpected_death(int sig) { } int main() { + signal(SIGCHLD, on_unexpected_death); // extract test program diff --git a/test/posix/forjustine_test.c b/test/posix/forjustine_test.c new file mode 100644 index 000000000..58663a2a3 --- /dev/null +++ b/test/posix/forjustine_test.c @@ -0,0 +1,49 @@ +// for justine with love 2025-01-02 +#include +#include +#include +#include +#include +#include +#include + +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; +static bool altstack_installed; + +static void* chump(void* v) { + stack_t* s = v; + if (sigaltstack(s, NULL)) { + pthread_mutex_lock(&lock); + altstack_installed = true; + pthread_mutex_unlock(&lock); + pthread_cond_signal(&cond); + return NULL; + } + pthread_mutex_lock(&lock); + altstack_installed = true; + pthread_cond_signal(&cond); + pthread_mutex_unlock(&lock); + while (1) + poll(NULL, 0, -1); + return NULL; +} + +int main(void) { + void* v; + stack_t s = {.ss_size = sysconf(_SC_SIGSTKSZ)}; + s.ss_sp = malloc(s.ss_size); + if (s.ss_sp == NULL) + return EXIT_FAILURE; + pthread_t tid; + if (pthread_create(&tid, NULL, chump, &s)) + return EXIT_FAILURE; + pthread_mutex_lock(&lock); + while (!altstack_installed) + pthread_cond_wait(&cond, &lock); + pthread_mutex_unlock(&lock); + free(s.ss_sp); + if (pthread_cancel(tid) || pthread_join(tid, &v)) + return EXIT_FAILURE; + return v == PTHREAD_CANCELED ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/test/posix/fork_bench_test.c b/test/posix/fork_bench_test.c new file mode 100644 index 000000000..6f962f89c --- /dev/null +++ b/test/posix/fork_bench_test.c @@ -0,0 +1,29 @@ +// Copyright 2024 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 +#include +#include "libc/testlib/benchmark.h" + +void TestFork(void) { + int pid; + if (!(pid = fork())) + _Exit(0); + wait(0); +} + +int main(int argc, char *argv[]) { + BENCHMARK(100, 1, TestFork()); +} diff --git a/test/posix/interprocess_signaling_test.c b/test/posix/interprocess_signaling_test.c new file mode 100644 index 000000000..d6372492e --- /dev/null +++ b/test/posix/interprocess_signaling_test.c @@ -0,0 +1,67 @@ +// Copyright 2024 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 +#include +#include +#include + +atomic_int *got; + +void onsig(int sig) { + *got = sig; +} + +int main(int argc, char *argv[]) { + + // create process shared memory + got = mmap(0, 4, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); + if (got == MAP_FAILED) + return 5; + + // listen for signal + if (signal(SIGUSR1, onsig)) + return 6; + + // block signals + sigset_t full; + if (sigfillset(&full)) + return 7; + if (sigprocmask(SIG_BLOCK, &full, 0)) + return 8; + + // create child process + int pid; + if (!(pid = fork())) { + sigset_t empty; + sigemptyset(&empty); + sigsuspend(&empty); + *got |= 128; + _exit(0); + } + + // send signal + if (kill(pid, SIGUSR1)) + return 9; + + // wait for child to die + int ws; + if (wait(&ws) != pid) + return 10; + if (ws) + return 11; + if (*got != (128 | SIGUSR1)) + return 12; +} diff --git a/test/posix/msg_nosignal_test.c b/test/posix/msg_nosignal_test.c new file mode 100644 index 000000000..c164a4b9b --- /dev/null +++ b/test/posix/msg_nosignal_test.c @@ -0,0 +1,196 @@ +// Copyright 2024 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @fileoverview send(MSG_NOSIGNAL) test + * + * It's possible when writing to a socket for SIGPIPE to be raised. It + * can happen for a variety of reasons. The one reason that has broad + * consensus across OSes and is officially documented, is if shutdown() + * is used on the local end. + */ + +struct sockaddr_in serv_addr; +atomic_bool g_ready_for_conn; +atomic_bool g_ready_for_data; +atomic_bool g_ready_for_more; +atomic_bool g_ready_for_exit; +volatile sig_atomic_t g_got_signal; + +void onsig(int sig) { + g_got_signal = sig; +} + +void *server_thread(void *arg) { + socklen_t len; + int server, client; + struct sockaddr_in cli_addr; + + // create listening socket + server = socket(AF_INET, SOCK_STREAM, 0); + if (server == -1) { + perror("socket"); + exit(10); + } + + // initialize server address + memset(&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + serv_addr.sin_port = htons(0); + + // bind socket + if (bind(server, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) { + perror("bind"); + exit(11); + } + + // get assigned port + len = sizeof(serv_addr); + if (getsockname(server, (struct sockaddr *)&serv_addr, &len)) { + perror("getsockname"); + exit(12); + } + + // listen on the socket + if (listen(server, SOMAXCONN)) { + perror("listen"); + exit(13); + } + + // wake main thread + g_ready_for_conn = true; + + // accept connection + len = sizeof(cli_addr); + client = accept(server, (struct sockaddr *)&cli_addr, &len); + if (client == -1) { + perror("accept"); + exit(14); + } + + // wake main thread + g_ready_for_data = true; + + // wait for thread + for (;;) + if (g_ready_for_exit) + break; + + // close sockets + if (close(client)) + exit(29); + if (close(server)) + exit(28); + + return 0; +} + +int main() { + + // create server thread + pthread_t th; + if (pthread_create(&th, 0, server_thread, 0)) + return 1; + + // wait for thread + for (;;) + if (g_ready_for_conn) + break; + + // create socket + int client = socket(AF_INET, SOCK_STREAM, 0); + if (client == -1) { + perror("socket"); + return 2; + } + + // connect to server + if (connect(client, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) { + perror("connect"); + return 3; + } + + // wait for thread + for (;;) + if (g_ready_for_data) + break; + + // handle signals + struct sigaction sa; + sa.sa_flags = 0; + sa.sa_handler = onsig; + sigemptyset(&sa.sa_mask); + sigaction(SIGPIPE, &sa, 0); + + // half close socket + if (shutdown(client, SHUT_WR)) + return 15; + + // send first transmission + int rc; + for (;;) { + rc = write(client, "x", 1); + if (rc == 1) + continue; + if (rc != -1) + return 4; + if (errno != EPIPE) { + perror("write"); + return 5; + } + // NetBSD is oddly lazy about sending SIGPIPE + if (IsNetbsd()) + for (;;) + if (g_got_signal) + break; + if (g_got_signal != SIGPIPE) { + fprintf(stderr, "expected SIGPIPE but got %s\n", strsignal(g_got_signal)); + return 6; + } + g_got_signal = 0; + break; + } + + // send first transmission + rc = send(client, "x", 1, MSG_NOSIGNAL); + if (rc != -1) + return 7; + if (errno != EPIPE) + return 8; + if (g_got_signal) + return 9; + + g_ready_for_exit = true; + + if (pthread_join(th, 0)) + return 6; +} diff --git a/test/posix/msg_waitall_test.c b/test/posix/msg_waitall_test.c new file mode 100644 index 000000000..99942383d --- /dev/null +++ b/test/posix/msg_waitall_test.c @@ -0,0 +1,207 @@ +// Copyright 2024 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @fileoverview recv(MSG_WAITALL) test + */ + +struct sockaddr_in serv_addr; +atomic_bool g_ready_for_conn; +atomic_bool g_ready_for_data; +atomic_bool g_ready_for_more; +atomic_bool g_ready_for_exit; + +void *server_thread(void *arg) { + socklen_t len; + int server, client; + struct sockaddr_in cli_addr; + + // create listening socket + server = socket(AF_INET, SOCK_STREAM, 0); + if (server == -1) { + perror("socket"); + exit(10); + } + + // initialize server address + memset(&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + serv_addr.sin_port = htons(0); + + // bind socket + if (bind(server, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) { + perror("bind"); + exit(11); + } + + // get assigned port + len = sizeof(serv_addr); + if (getsockname(server, (struct sockaddr *)&serv_addr, &len)) { + perror("getsockname"); + exit(12); + } + + // listen on the socket + if (listen(server, SOMAXCONN)) { + perror("listen"); + exit(13); + } + + // wake main thread + g_ready_for_conn = true; + + // accept connection + len = sizeof(cli_addr); + client = accept(server, (struct sockaddr *)&cli_addr, &len); + if (client == -1) { + perror("accept"); + exit(14); + } + + // check waitall + dontwait + char buf[2]; + int rc = recv(client, buf, 2, MSG_WAITALL | MSG_DONTWAIT); + if (rc != -1) + exit(15); + if (errno != EAGAIN) + exit(16); + + // wake main thread + g_ready_for_data = true; + + // check peek + rc = recv(client, buf, 2, MSG_PEEK); + if (rc == -1) { + perror("recv1"); + exit(17); + } + if (rc != 1) + exit(18); + if (buf[0] != 'x') + exit(19); + + // check read() has @restartable behavior + rc = recv(client, buf, 2, MSG_WAITALL); + if (rc == -1) { + perror("recv2"); + exit(21); + } + if (rc != 2) + exit(22); + if (buf[0] != 'x') + exit(23); + if (buf[1] != 'y') + exit(24); + + // wake main thread + g_ready_for_more = true; + + // check normal recv won't wait + rc = read(client, buf, 2); + if (rc == -1) { + perror("recv3"); + exit(25); + } + if (rc != 1) + exit(26); + if (buf[0] != 'x') + exit(27); + + // wait for main thread + for (;;) + if (g_ready_for_exit) + break; + + // close listening socket + if (close(server)) + exit(28); + if (close(client)) + exit(29); + return 0; +} + +int main() { + + // create server thread + pthread_t th; + if (pthread_create(&th, 0, server_thread, 0)) + return 1; + + // wait for thread + for (;;) + if (g_ready_for_conn) + break; + + // create socket + int client = socket(AF_INET, SOCK_STREAM, 0); + if (client == -1) { + perror("socket"); + return 2; + } + + // connect to server + if (connect(client, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) { + perror("connect"); + return 3; + } + + // wait for thread + for (;;) + if (g_ready_for_data) + break; + + // send first transmission + usleep(100e3); + if (write(client, "x", 1) != 1) + return 4; + usleep(100e3); + if (write(client, "y", 1) != 1) + return 5; + + // wait for thread + for (;;) + if (g_ready_for_more) + break; + + // send second transmission + usleep(100e3); + if (write(client, "x", 1) != 1) + return 4; + usleep(100e3); + if (write(client, "y", 1) != 1) + return 5; + + g_ready_for_exit = true; + + if (pthread_join(th, 0)) + return 6; +} diff --git a/test/posix/mutex_async_signal_safety_test.c b/test/posix/mutex_async_signal_safety_test.c deleted file mode 100644 index 08cc268e8..000000000 --- a/test/posix/mutex_async_signal_safety_test.c +++ /dev/null @@ -1,86 +0,0 @@ -#include -#include -#include -#include -#include -#include - -// tests that recursive mutexes are implemented atomically -// -// glibc fails this test -// musl passes this test -// cosmo only guarantees this in process shared mode - -atomic_bool done; -atomic_bool ready; -pthread_mutex_t lock; - -void hand(int sig) { - if (pthread_mutex_lock(&lock)) - _Exit(50); - if (pthread_mutex_unlock(&lock)) - _Exit(51); -} - -void* work(void* arg) { - ready = true; - while (!done) { - if (pthread_mutex_lock(&lock)) - _Exit(60); - if (pthread_mutex_unlock(&lock)) - _Exit(61); - } - return 0; -} - -int main() { - - struct sigaction sa; - sa.sa_handler = hand; - sa.sa_flags = SA_NODEFER; - sigemptyset(&sa.sa_mask); - if (sigaction(SIGUSR1, &sa, 0)) - _Exit(1); - - pthread_mutexattr_t attr; - if (pthread_mutexattr_init(&attr)) - _Exit(2); - if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) - _Exit(3); - if (pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED)) - _Exit(3); - if (pthread_mutex_init(&lock, &attr)) - _Exit(4); - if (pthread_mutexattr_destroy(&attr)) - _Exit(5); - - pthread_t th; - pthread_attr_t tattr; - if (pthread_attr_init(&tattr)) - _Exit(6); - if (pthread_attr_setstacksize(&tattr, 8 * 1024 * 1024)) - _Exit(7); - if (pthread_attr_setguardsize(&tattr, 64 * 1024)) - _Exit(8); - if (pthread_create(&th, &tattr, work, 0)) - _Exit(9); - if (pthread_attr_destroy(&tattr)) - _Exit(10); - for (;;) - if (ready) - break; - - for (int i = 0; i < 100; ++i) { - if (pthread_kill(th, SIGUSR1)) - _Exit(11); - if (pthread_kill(th, SIGUSR1)) - _Exit(12); - usleep(1); - } - - done = true; - if (pthread_join(th, 0)) - _Exit(13); - if (pthread_mutex_destroy(&lock)) - _Exit(14); -} diff --git a/test/posix/pending_signal_execve_test.c b/test/posix/pending_signal_execve_test.c new file mode 100644 index 000000000..0b97b794b --- /dev/null +++ b/test/posix/pending_signal_execve_test.c @@ -0,0 +1,59 @@ +// Copyright 2024 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 +#include +#include +#include +#include + +sig_atomic_t gotsig; + +void onsig(int sig) { + gotsig = sig; +} + +int main(int argc, char* argv[]) { + sigset_t ss; + sigfillset(&ss); + sigprocmask(SIG_BLOCK, &ss, 0); + if (argc >= 2 && !strcmp(argv[1], "childe")) { + signal(SIGUSR1, onsig); + sigemptyset(&ss); + sigsuspend(&ss); + if (gotsig != SIGUSR1) + return 2; + } else { + int child; + if ((child = fork()) == -1) + return 2; + if (!child) { + execlp(argv[0], argv[0], "childe", NULL); + _Exit(127); + } + if (IsNetbsd() || IsOpenbsd()) { + // NetBSD has a bug where pending signals don't inherit across + // execve, even though POSIX.1 literally says you must do this + sleep(1); + } + if (kill(child, SIGUSR1)) + return 3; + int ws; + if (wait(&ws) != child) + return 4; + if (ws) + return 5; + } +} diff --git a/test/posix/pipe_write_eagain_test.c b/test/posix/pipe_write_eagain_test.c new file mode 100644 index 000000000..66a1c42ea --- /dev/null +++ b/test/posix/pipe_write_eagain_test.c @@ -0,0 +1,107 @@ +// Copyright 2024 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 +#include +#include +#include +#include +#include + +/** + * @fileoverview Tests that EAGAIN won't corrupt pipe. + * + * This is a real bug when using CancelIoEx() on winsock writes, so we + * need to make sure it doesn't happen on pipes too. + */ + +#define ITERATIONS 100000 +#define ASYMMETRY 3 + +int fds[2]; +int got_read_eagains; +int got_write_eagains; + +void *worker(void *arg) { + for (int expect = 0; expect < ITERATIONS;) { + int number; + ssize_t rc = read(fds[0], &number, sizeof(number)); + if (rc == -1) { + if (errno == EAGAIN) { + ++got_read_eagains; + if (poll(&(struct pollfd){fds[0], POLLIN}, 1, -1) == -1) + exit(11); + continue; + } + perror("read"); + exit(8); + } + size_t got = rc; + if (got != sizeof(int)) + exit(9); + if (expect != number) + exit(10); + ++expect; + } + return 0; +} + +int main(int argc, char *argv[]) { + + if (pipe2(fds, O_NONBLOCK)) + return 1; + + pthread_t th; + if (pthread_create(&th, 0, worker, 0)) + return 2; + + int number = 0; + for (;;) { + int chunk = 0; + int numbers[ASYMMETRY]; + for (;;) { + numbers[chunk] = number + chunk; + if (++chunk == ASYMMETRY) + break; + if (number + chunk == ITERATIONS) + break; + } + for (;;) { + ssize_t rc = write(fds[1], numbers, chunk * sizeof(int)); + if (rc == -1) { + if (errno == EAGAIN) { + ++got_write_eagains; + if (poll(&(struct pollfd){fds[1], POLLOUT}, 1, -1) == -1) + return 10; + continue; + } + return 3; + } + if (rc % sizeof(int)) + return 4; + chunk = rc / sizeof(int); + number += chunk; + break; + } + if (number == ITERATIONS) + break; + } + + if (pthread_join(th, 0)) + return 5; + + if (!got_read_eagains && !got_write_eagains) + return 7; +} diff --git a/test/posix/printf_return_test.c b/test/posix/printf_return_test.c new file mode 100644 index 000000000..377bb9c7f --- /dev/null +++ b/test/posix/printf_return_test.c @@ -0,0 +1,43 @@ +// Copyright 2024 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 +#include +#include + +int main() { + + if (close(1)) + return 1; + if (open("/dev/null", O_WRONLY) != 1) + return 2; + + if (printf("a") != 1) + return 4; + if (printf("%s", "") != 0) + return 5; + if (printf("%s", "a") != 1) + return 6; + if (printf("%10s", "a") != 10) + return 6; + if (printf("%-10s", "a") != 10) + return 6; + if (printf("%-10s%-40s %9s %8s %8s %8s\n", "Benchmark", "prog", "ops", + "time", "ops/sec", "time/op") != 89) + return 7; + if (fprintf(stdout, "%-10s%-40s %9s %8s %8s %8s\n", "Benchmark", "prog", + "ops", "time", "ops/sec", "time/op") != 89) + return 8; +} diff --git a/test/posix/sa_resethand2_test.c b/test/posix/sa_resethand2_test.c index c66f8cb8d..3a6dc34da 100644 --- a/test/posix/sa_resethand2_test.c +++ b/test/posix/sa_resethand2_test.c @@ -1,27 +1,20 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2023 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/sigaction.h" -#include "libc/calls/struct/sigset.h" -#include "libc/dce.h" -#include "libc/sysv/consts/sa.h" -#include "libc/sysv/consts/sig.h" +// Copyright 2024 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 +#include volatile int handler_invoked; @@ -33,24 +26,17 @@ int main() { sigset_t mask, oldmask; struct sigaction sa, current_sa; - if (IsWindows()) { - // TODO(jart): support non-fatal signals between processes - return 0; - } - sa.sa_handler = signal_handler; sa.sa_flags = SA_RESETHAND; sigemptyset(&sa.sa_mask); - if (sigaction(SIGINT, &sa, 0) == -1) { + if (sigaction(SIGINT, &sa, 0) == -1) return 1; - } sigemptyset(&mask); sigaddset(&mask, SIGINT); - if (sigprocmask(SIG_BLOCK, &mask, &oldmask) == -1) { + if (sigprocmask(SIG_BLOCK, &mask, &oldmask) == -1) return 2; - } int pid = fork(); if (pid == -1) { @@ -60,15 +46,12 @@ int main() { return 0; } else { sigsuspend(&oldmask); - if (!handler_invoked) { + if (!handler_invoked) return 4; - } - if (sigaction(SIGINT, 0, ¤t_sa) == -1) { + if (sigaction(SIGINT, 0, ¤t_sa) == -1) return 5; - } - if (current_sa.sa_handler != SIG_DFL) { + if (current_sa.sa_handler != SIG_DFL) return 6; - } return 0; } } diff --git a/test/posix/sa_resethand_test.c b/test/posix/sa_resethand_test.c index c582d90fe..eed501bf6 100644 --- a/test/posix/sa_resethand_test.c +++ b/test/posix/sa_resethand_test.c @@ -1,22 +1,21 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2023 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. │ -╚─────────────────────────────────────────────────────────────────────────────*/ +// Copyright 2024 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 #include +#include volatile int gotsig; @@ -24,23 +23,68 @@ void OnSig(int sig) { gotsig = sig; } -int main() { +void test_sa_resethand_raise(void) { struct sigaction sa; sa.sa_handler = OnSig; sa.sa_flags = SA_RESETHAND; sigemptyset(&sa.sa_mask); if (sigaction(SIGUSR1, &sa, 0)) - return 1; + exit(1); if (sigaction(SIGUSR1, 0, &sa)) - return 2; + exit(2); if (sa.sa_handler != OnSig) - return 3; + exit(3); if (raise(SIGUSR1)) - return 4; + exit(4); if (gotsig != SIGUSR1) - return 5; + exit(5); if (sigaction(SIGUSR1, 0, &sa)) - return 6; + exit(6); if (sa.sa_handler != SIG_DFL) - return 7; + exit(7); +} + +void test_sa_resethand_pause(void) { + struct sigaction sa; + sa.sa_handler = OnSig; + sa.sa_flags = SA_RESETHAND; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGALRM, &sa, 0)) + exit(10); + ualarm(10000, 0); + if (pause() != -1 || errno != EINTR) + exit(11); + if (gotsig != SIGALRM) + exit(12); + if (sigaction(SIGALRM, 0, &sa)) + exit(13); + if (sa.sa_handler != SIG_DFL) + exit(14); +} + +void test_sa_resethand_read(void) { + struct sigaction sa; + sa.sa_handler = OnSig; + sa.sa_flags = SA_RESETHAND; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGALRM, &sa, 0)) + exit(20); + int fds[2]; + if (pipe(fds)) + exit(21); + ualarm(10000, 0); + if (read(fds[0], (char[]){0}, 1) != -1 || errno != EINTR) + exit(22); + if (gotsig != SIGALRM) + exit(23); + if (sigaction(SIGALRM, 0, &sa)) + exit(24); + if (sa.sa_handler != SIG_DFL) + exit(25); +} + +int main() { + test_sa_resethand_raise(); + test_sa_resethand_pause(); + test_sa_resethand_read(); } diff --git a/test/posix/sigchld_test.c b/test/posix/sigchld_test.c index 36cf1f032..6915f7cf2 100644 --- a/test/posix/sigchld_test.c +++ b/test/posix/sigchld_test.c @@ -32,7 +32,7 @@ #include // clang-format off -// sh -c 'build/bootstrap/make -j8 V=1 o//test/posix/sigchld_test.runs' +// sh -c '.cosmocc/current/bin/make -j8 V=1 o//test/posix/sigchld_test.runs' // clang-format on void Assert(const char *file, int line, bool ok) { diff --git a/test/posix/signal_fight_test.c b/test/posix/signal_fight_test.c new file mode 100644 index 000000000..910a7e04a --- /dev/null +++ b/test/posix/signal_fight_test.c @@ -0,0 +1,105 @@ +// Copyright 2024 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 +#include +#include + +/** + * @fileoverview Tests two threads killing each other won't deadlock. + * + * Our Windows implementation of signals has surprisingly high + * throughput on this test. About 10x more signals get delivered than + * any other OS and in the same amount of time. The only exception was + * OpenBSD, which delivered a similar number of signals, but it took 10x + * longer for the process to execute. + */ + +#define ITERATIONS 10000 + +int gotsigs[2]; +pthread_t threads[2]; +pthread_t thread_ids[2]; +pthread_barrier_t barrier; +pthread_barrier_t barrier2; + +void sig_handler(int signo) { + if (pthread_equal(pthread_self(), threads[0])) + ++gotsigs[0]; + if (pthread_equal(pthread_self(), threads[1])) + ++gotsigs[1]; +} + +void *thread_func(void *arg) { + int idx = *(int *)arg; + int other_idx = 1 - idx; + + thread_ids[idx] = pthread_self(); + + int s = pthread_barrier_wait(&barrier); + if (s != 0 && s != PTHREAD_BARRIER_SERIAL_THREAD) + exit(1); + + pthread_t other_thread = thread_ids[other_idx]; + + for (int i = 0; i < ITERATIONS; ++i) + if (pthread_kill(other_thread, SIGUSR1)) + exit(2); + + s = pthread_barrier_wait(&barrier2); + if (s != 0 && s != PTHREAD_BARRIER_SERIAL_THREAD) + exit(1); + + return 0; +} + +int main() { + struct sigaction sa; + sa.sa_handler = sig_handler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + + if (sigaction(SIGUSR1, &sa, 0) == -1) + exit(3); + + if (pthread_barrier_init(&barrier, 0, 2)) + exit(4); + if (pthread_barrier_init(&barrier2, 0, 2)) + exit(4); + + int idx0 = 0, idx1 = 1; + + if (pthread_create(&threads[0], 0, thread_func, &idx0)) + exit(5); + if (pthread_create(&threads[1], 0, thread_func, &idx1)) + exit(6); + + if (pthread_join(threads[0], 0)) + exit(7); + if (pthread_join(threads[1], 0)) + exit(8); + + if (pthread_barrier_destroy(&barrier2)) + exit(9); + if (pthread_barrier_destroy(&barrier)) + exit(9); + + if (!gotsigs[0]) + exit(10); + if (!gotsigs[1]) + exit(11); + + return 0; +} diff --git a/test/posix/signal_latency_async_test.c b/test/posix/signal_latency_async_test.c new file mode 100644 index 000000000..d507d8b1a --- /dev/null +++ b/test/posix/signal_latency_async_test.c @@ -0,0 +1,145 @@ +// Copyright 2024 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 +#include +#include +#include +#include +#include +#include +#include + +#define ITERATIONS 10000 + +pthread_t sender_thread; +pthread_t receiver_thread; +struct timespec send_time; +atomic_int sender_got_signal; +atomic_int receiver_got_signal; +double latencies[ITERATIONS]; + +void sender_signal_handler(int signo) { + sender_got_signal = 1; +} + +void receiver_signal_handler(int signo) { + receiver_got_signal = 1; +} + +void *sender_func(void *arg) { + for (int i = 0; i < ITERATIONS; i++) { + + // Wait a bit sometimes + if (rand() % 2) { + volatile unsigned v = 0; + for (;;) + if (++v == 4000) + break; + } + + // Ping receiver + clock_gettime(CLOCK_MONOTONIC, &send_time); + if (pthread_kill(receiver_thread, SIGUSR1)) + exit(6); + + // Wait for pong + for (;;) + if (atomic_load_explicit(&sender_got_signal, memory_order_relaxed)) + break; + sender_got_signal = 0; + } + + return 0; +} + +void *receiver_func(void *arg) { + static int iteration = 0; + do { + // wait for signal handler to be called + if (atomic_exchange_explicit(&receiver_got_signal, 0, + memory_order_acq_rel)) { + + // record received time + struct timespec receive_time; + clock_gettime(CLOCK_MONOTONIC, &receive_time); + long sec_diff = receive_time.tv_sec - send_time.tv_sec; + long nsec_diff = receive_time.tv_nsec - send_time.tv_nsec; + double latency_ns = sec_diff * 1e9 + nsec_diff; + latencies[iteration++] = latency_ns; + + // pong sender + if (pthread_kill(sender_thread, SIGUSR2)) + exit(2); + } + } while (iteration < ITERATIONS); + return 0; +} + +int compare(const void *a, const void *b) { + const double *x = a, *y = b; + if (*x < *y) + return -1; + else if (*x > *y) + return 1; + else + return 0; +} + +int main() { + + // install handlers + struct sigaction sa; + sa.sa_handler = receiver_signal_handler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(SIGUSR1, &sa, 0); + sa.sa_handler = sender_signal_handler; + sigaction(SIGUSR2, &sa, 0); + + // create receiver thread first + if (pthread_create(&receiver_thread, 0, receiver_func, 0)) + exit(11); + + // create sender thread + if (pthread_create(&sender_thread, 0, sender_func, 0)) + exit(12); + + // wait for threads to finish + if (pthread_join(sender_thread, 0)) + exit(13); + if (pthread_join(receiver_thread, 0)) + exit(14); + + // compute mean latency + double total_latency = 0; + for (int i = 0; i < ITERATIONS; i++) + total_latency += latencies[i]; + double mean_latency = total_latency / ITERATIONS; + + // sort latencies to compute percentiles + qsort(latencies, ITERATIONS, sizeof(double), compare); + + double p50 = latencies[(int)(0.50 * ITERATIONS)]; + double p90 = latencies[(int)(0.90 * ITERATIONS)]; + double p95 = latencies[(int)(0.95 * ITERATIONS)]; + double p99 = latencies[(int)(0.99 * ITERATIONS)]; + + printf("Mean latency: %.2f ns\n", mean_latency); + printf("50th percentile latency: %.2f ns\n", p50); + printf("90th percentile latency: %.2f ns\n", p90); + printf("95th percentile latency: %.2f ns\n", p95); + printf("99th percentile latency: %.2f ns\n", p99); +} diff --git a/test/posix/signal_latency_test.c b/test/posix/signal_latency_test.c new file mode 100644 index 000000000..aa73cb771 --- /dev/null +++ b/test/posix/signal_latency_test.c @@ -0,0 +1,177 @@ +// Copyright 2024 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ITERATIONS 10000 + +atomic_bool got_sigusr2; +pthread_t sender_thread; +pthread_t receiver_thread; +struct timespec send_time; +double latencies[ITERATIONS]; + +void sender_signal_handler(int signo) { + got_sigusr2 = true; +} + +void receiver_signal_handler(int signo) { + struct timespec receive_time; + if (clock_gettime(CLOCK_MONOTONIC, &receive_time) == -1) + exit(1); + + long sec_diff = receive_time.tv_sec - send_time.tv_sec; + long nsec_diff = receive_time.tv_nsec - send_time.tv_nsec; + double latency_ns = sec_diff * 1e9 + nsec_diff; + + static int iteration = 0; + if (iteration < ITERATIONS) + latencies[iteration++] = latency_ns; + + // Send SIGUSR2 back to sender_thread + if (pthread_kill(sender_thread, SIGUSR2)) + exit(2); + + // Exit if we're done. + if (iteration >= ITERATIONS) + pthread_exit(0); +} + +void *sender_func(void *arg) { + // Block SIGUSR2 + sigset_t block_set; + sigemptyset(&block_set); + sigaddset(&block_set, SIGUSR2); + if (pthread_sigmask(SIG_BLOCK, &block_set, 0)) + exit(3); + + // Install signal handler for SIGUSR2 + struct sigaction sa; + sa.sa_handler = sender_signal_handler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGUSR2, &sa, 0)) + exit(4); + + for (int i = 0; i < ITERATIONS; i++) { + if (clock_gettime(CLOCK_MONOTONIC, &send_time)) + exit(5); + + // Send SIGUSR1 to receiver_thread + got_sigusr2 = false; + if (pthread_kill(receiver_thread, SIGUSR1)) + exit(6); + + // Unblock SIGUSR2 and wait for it + sigset_t wait_set; + sigemptyset(&wait_set); + while (!got_sigusr2) + if (sigsuspend(&wait_set) && errno != EINTR) + exit(7); + } + + return 0; +} + +void *receiver_func(void *arg) { + // Install signal handler for SIGUSR1 + struct sigaction sa; + sa.sa_handler = receiver_signal_handler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGUSR1, &sa, 0)) + exit(8); + + // Block all signals except SIGUSR1 + sigset_t block_set; + sigfillset(&block_set); + sigdelset(&block_set, SIGUSR1); + if (pthread_sigmask(SIG_SETMASK, &block_set, 0)) + exit(9); + + // Wait indefinitely for signals + while (1) + pause(); + + return 0; +} + +int compare(const void *a, const void *b) { + const double *x = a, *y = b; + if (*x < *y) + return -1; + else if (*x > *y) + return 1; + else + return 0; +} + +int main() { + + // this test probably exposes a bug in openbsd + if (IsOpenbsd()) + return 0; + + // Block SIGUSR1 and SIGUSR2 in main thread + sigset_t block_set; + sigemptyset(&block_set); + sigaddset(&block_set, SIGUSR1); + sigaddset(&block_set, SIGUSR2); + if (pthread_sigmask(SIG_BLOCK, &block_set, 0)) + exit(10); + + // Create receiver thread first + if (pthread_create(&receiver_thread, 0, receiver_func, 0)) + exit(11); + + // Create sender thread + if (pthread_create(&sender_thread, 0, sender_func, 0)) + exit(12); + + // Wait for threads to finish + if (pthread_join(sender_thread, 0)) + exit(13); + if (pthread_join(receiver_thread, 0)) + exit(14); + + // Compute mean latency + double total_latency = 0; + for (int i = 0; i < ITERATIONS; i++) + total_latency += latencies[i]; + double mean_latency = total_latency / ITERATIONS; + + // Sort latencies to compute percentiles + qsort(latencies, ITERATIONS, sizeof(double), compare); + + double p50 = latencies[(int)(0.50 * ITERATIONS)]; + double p90 = latencies[(int)(0.90 * ITERATIONS)]; + double p95 = latencies[(int)(0.95 * ITERATIONS)]; + double p99 = latencies[(int)(0.99 * ITERATIONS)]; + + printf("Mean latency: %.2f ns\n", mean_latency); + printf("50th percentile latency: %.2f ns\n", p50); + printf("90th percentile latency: %.2f ns\n", p90); + printf("95th percentile latency: %.2f ns\n", p95); + printf("99th percentile latency: %.2f ns\n", p99); +} diff --git a/test/posix/wait2x_test.c b/test/posix/wait2x_test.c new file mode 100644 index 000000000..06c900ec4 --- /dev/null +++ b/test/posix/wait2x_test.c @@ -0,0 +1,70 @@ +// Copyright 2024 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 +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) { + int ws, rc; + + // create shared memory for synchronization + atomic_int *ready = + mmap(0, 4, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); + + if ((rc = waitpid(-1, &ws, WNOHANG)) != -1) + return 1; + if (errno != ECHILD) + return 2; + + // create process + int pid = fork(); + if (pid == -1) + return 3; + if (!pid) { + for (;;) + if (*ready) + break; + _Exit(0); + } + + // wait on process + if ((rc = waitpid(pid, &ws, WNOHANG)) == -1) + return 4; + if (rc != 0) + return 5; + if (ws) + return 6; + + // signal subprocess + *ready = 1; + + if ((rc = waitpid(pid, &ws, 0)) == -1) + return 7; + if (rc != pid) + return 8; + if (ws) + return 9; + + // wait again + if ((rc = waitpid(pid, &ws, WNOHANG)) != -1) + return 10; + if (errno != ECHILD) + return 11; +} diff --git a/test/tool/args/args2_test.c b/test/tool/args/args2_test.c new file mode 100644 index 000000000..8f208aa63 --- /dev/null +++ b/test/tool/args/args2_test.c @@ -0,0 +1,195 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 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/cosmo.h" +#include "libc/dce.h" +#include "libc/mem/mem.h" +#include "libc/runtime/runtime.h" +#include "libc/stdio/rand.h" +#include "libc/testlib/testlib.h" +#include "libc/x/x.h" + +void SetUpOnce(void) { + testlib_enable_tmp_setup_teardown(); +} + +TEST(cosmo_args, normalize) { + char *args[] = {0}; + char **argv = args; + ASSERT_EQ(1, cosmo_args(0, &argv)); + ASSERT_STREQ(GetProgramExecutableName(), argv[0]); +} + +TEST(cosmo_args, test) { + xbarf(".args", "a b c", -1); + char *args[] = {"prog", "arg", 0}; + char **argv = args; + ASSERT_EQ(5, cosmo_args(".args", &argv)); + ASSERT_STREQ("prog", argv[0]); + ASSERT_STREQ("a", argv[1]); + ASSERT_STREQ("b", argv[2]); + ASSERT_STREQ("c", argv[3]); + ASSERT_STREQ("arg", argv[4]); +} + +TEST(cosmo_args, perline) { + xbarf(".args", "a\nb\nc\n", -1); + char *args[] = {"prog", "arg", 0}; + char **argv = args; + ASSERT_EQ(5, cosmo_args(".args", &argv)); + ASSERT_STREQ("prog", argv[0]); + ASSERT_STREQ("a", argv[1]); + ASSERT_STREQ("b", argv[2]); + ASSERT_STREQ("c", argv[3]); + ASSERT_STREQ("arg", argv[4]); +} + +TEST(cosmo_args, dots_end) { + xbarf(".args", "a b c ...", -1); + char *args[] = {"prog", "arg", 0}; + char **argv = args; + ASSERT_EQ(5, cosmo_args(".args", &argv)); + ASSERT_STREQ("prog", argv[0]); + ASSERT_STREQ("a", argv[1]); + ASSERT_STREQ("b", argv[2]); + ASSERT_STREQ("c", argv[3]); + ASSERT_STREQ("arg", argv[4]); +} + +TEST(cosmo_args, dots_middle) { + xbarf(".args", "a ... b c", -1); + char *args[] = {"prog", "arg", 0}; + char **argv = args; + ASSERT_EQ(5, cosmo_args(".args", &argv)); + ASSERT_STREQ("prog", argv[0]); + ASSERT_STREQ("a", argv[1]); + ASSERT_STREQ("arg", argv[2]); + ASSERT_STREQ("b", argv[3]); + ASSERT_STREQ("c", argv[4]); +} + +TEST(cosmo_args, quote) { + xbarf(".args", " 'hi \\n there'# ", -1); + char *args[] = {0}; + char **argv = args; + ASSERT_EQ(2, cosmo_args(".args", &argv)); + ASSERT_STREQ("hi \\n there#", argv[1]); +} + +TEST(cosmo_args, dquote) { + xbarf(".args", " \"hi \\a\\b\\t\\n\\v\\f\\r\\e\\0\\11 \\111 \xab there\"# ", + -1); + char *args[] = {0}; + char **argv = args; + ASSERT_EQ(2, cosmo_args(".args", &argv)); + ASSERT_STREQ("hi \a\b\t\n\v\f\r\e\0\11 \111 \xab there#", argv[1]); +} + +TEST(cosmo_args, comment) { + xbarf(".args", + "# comment\n" + "a # hello there\n" + "b # yup\n", + -1); + char *args[] = {0}; + char **argv = args; + ASSERT_EQ(3, cosmo_args(".args", &argv)); + ASSERT_STREQ("a", argv[1]); + ASSERT_STREQ("b", argv[2]); +} + +TEST(cosmo_args, backslash_newline) { + xbarf(".args", + "a\\\n" + "b\n", + -1); + char *args[] = {0}; + char **argv = args; + ASSERT_EQ(2, cosmo_args(".args", &argv)); + ASSERT_STREQ("ab", argv[1]); +} + +TEST(cosmo_args, dotz) { + xbarf(".args", ". .. ...x", -1); + char *args[] = {0}; + char **argv = args; + ASSERT_EQ(4, cosmo_args(".args", &argv)); + ASSERT_STREQ(".", argv[1]); + ASSERT_STREQ("..", argv[2]); + ASSERT_STREQ("...x", argv[3]); +} + +TEST(cosmo_args, env) { + setenv("foo", "bar", true); + xbarf(".args", "$foo x${foo}x \"$foo\" \"${foo}\" $foo", -1); + char *args[] = {0}; + char **argv = args; + ASSERT_EQ(6, cosmo_args(".args", &argv)); + ASSERT_STREQ("bar", argv[1]); + ASSERT_STREQ("xbarx", argv[2]); + ASSERT_STREQ("bar", argv[3]); + ASSERT_STREQ("bar", argv[4]); + ASSERT_STREQ("bar", argv[5]); +} + +TEST(cosmo_args, dquote_backslash_newline) { + setenv("foo", "bar", true); + xbarf(".args", + "-p \"\\\n" + "hello\"\n", + -1); + char *args[] = {0}; + char **argv = args; + ASSERT_EQ(3, cosmo_args(".args", &argv)); + ASSERT_STREQ("-p", argv[1]); + ASSERT_STREQ("hello", argv[2]); +} + +TEST(cosmo_args, dquote_plain_old_newline) { + setenv("foo", "bar", true); + xbarf(".args", + "-p \"\n" + "hello\"\n", + -1); + char *args[] = {0}; + char **argv = args; + ASSERT_EQ(3, cosmo_args(".args", &argv)); + ASSERT_STREQ("-p", argv[1]); + ASSERT_STREQ("\nhello", argv[2]); +} + +#define LENGTH 128 +#define ITERATIONS 5000 +#define CHARSET "abc#'\"$.\\{} \r\n" + +TEST(cosmo_args, fuzz) { + if (IsWindows()) + return; // not worth it fs too slow + char s[LENGTH + 1] = {0}; + for (int i = 0; i < ITERATIONS; ++i) { + for (int j = 0; j < LENGTH; ++j) + s[j] = CHARSET[rand() % (sizeof(CHARSET) - 1)]; + xbarf(".args", s, -1); + char *args[] = {0}; + char **argv = args; + cosmo_args(".args", &argv); + for (int j = 0; argv[j]; ++j) + free(argv[j]); + argv[0] = 0; + } +} diff --git a/test/tool/net/redbean_test.c b/test/tool/net/redbean_test.c index b25010030..e13c3cfdd 100644 --- a/test/tool/net/redbean_test.c +++ b/test/tool/net/redbean_test.c @@ -39,7 +39,6 @@ #include "libc/testlib/testlib.h" #include "libc/x/x.h" #include "third_party/regex/regex.h" -#ifdef __x86_64__ __static_yoink("zipos"); __static_yoink("o/" MODE "/test/tool/net/redbean-tester"); @@ -292,5 +291,3 @@ Z\n", EXPECT_NE(-1, wait(0)); EXPECT_NE(-1, sigprocmask(SIG_SETMASK, &savemask, 0)); } - -#endif /* __x86_64__ */ diff --git a/third_party/awk/BUILD.mk b/third_party/awk/BUILD.mk index 591a6f41d..da4affc22 100644 --- a/third_party/awk/BUILD.mk +++ b/third_party/awk/BUILD.mk @@ -22,11 +22,12 @@ THIRD_PARTY_AWK_A_DIRECTDEPS = \ LIBC_RUNTIME \ LIBC_STDIO \ LIBC_STR \ + LIBC_SYSTEM \ LIBC_SYSV \ LIBC_TINYMATH \ - TOOL_ARGS \ THIRD_PARTY_GDTOA \ THIRD_PARTY_MUSL \ + TOOL_ARGS \ THIRD_PARTY_AWK_A_DEPS := \ $(call uniq,$(foreach x,$(THIRD_PARTY_AWK_A_DIRECTDEPS),$($(x)))) diff --git a/third_party/compiler_rt/clear_cache.c b/third_party/compiler_rt/clear_cache.c index 7486b0966..8f3cd9cb2 100644 --- a/third_party/compiler_rt/clear_cache.c +++ b/third_party/compiler_rt/clear_cache.c @@ -15,7 +15,7 @@ // It is expected to invalidate the instruction cache for the // specified range. -void __clear_cache(void *start, void *end) { +privileged void __clear_cache(void *start, void *end) { #ifdef __aarch64__ if (IsXnu()) { @@ -59,6 +59,8 @@ void __clear_cache(void *start, void *end) { } __asm__ volatile("isync"); +#elif defined(__x86_64__) + // do nothing #else compilerrt_abort(); #endif diff --git a/third_party/ctags/BUILD.mk b/third_party/ctags/BUILD.mk index 826ceefe4..973ce3a02 100644 --- a/third_party/ctags/BUILD.mk +++ b/third_party/ctags/BUILD.mk @@ -19,13 +19,14 @@ THIRD_PARTY_CTAGS_DIRECTDEPS = \ LIBC_LOG \ LIBC_MEM \ LIBC_NEXGEN32E \ - LIBC_RUNTIME \ LIBC_PROC \ + LIBC_RUNTIME \ LIBC_STDIO \ LIBC_STR \ + LIBC_SYSTEM \ LIBC_SYSV \ THIRD_PARTY_MUSL \ - THIRD_PARTY_REGEX + THIRD_PARTY_REGEX \ THIRD_PARTY_CTAGS_DEPS := \ $(call uniq,$(foreach x,$(THIRD_PARTY_CTAGS_DIRECTDEPS),$($(x)))) diff --git a/third_party/dlmalloc/README.cosmo b/third_party/dlmalloc/README.cosmo index 0db6ea937..097b9342a 100644 --- a/third_party/dlmalloc/README.cosmo +++ b/third_party/dlmalloc/README.cosmo @@ -9,6 +9,7 @@ LICENSE LOCAL CHANGES + - Fix MT-safety bugs in DEBUG mode - Fix bug in dlmalloc_inspect_all() - Define dlmalloc_requires_more_vespene_gas() - Make dlmalloc scalable using sched_getcpu() diff --git a/third_party/dlmalloc/dlmalloc.c b/third_party/dlmalloc/dlmalloc.c index 389fff109..b20e28cd9 100644 --- a/third_party/dlmalloc/dlmalloc.c +++ b/third_party/dlmalloc/dlmalloc.c @@ -31,23 +31,20 @@ #define FOOTERS 1 #define MSPACES 1 #define ONLY_MSPACES 1 // enables scalable multi-threaded malloc -#define USE_SPIN_LOCKS 0 // only profitable using sched_getcpu() +#define USE_SPIN_LOCKS 0 // set to 0 to use scalable nsync locks #else #define INSECURE 1 #define PROCEED_ON_ERROR 1 #define FOOTERS 0 #define MSPACES 0 #define ONLY_MSPACES 0 +#define USE_SPIN_LOCKS 1 #endif -#define HAVE_MMAP 1 #define HAVE_MREMAP 1 -#define HAVE_MORECORE 0 #define USE_LOCKS 2 -#define MORECORE_CONTIGUOUS 0 #define MALLOC_INSPECT_ALL 1 #define ABORT_ON_ASSERT_FAILURE 0 -#define LOCK_AT_FORK 1 #define NO_MALLOC_STATS 1 #if IsModeDbg() @@ -65,11 +62,6 @@ #include "locks.inc" #include "chunks.inc" #include "headfoot.inc" - -#if ONLY_MSPACES -#include "threaded.inc" -#endif - #include "global.inc" #include "system.inc" #include "hooks.inc" @@ -77,6 +69,11 @@ #include "indexing.inc" #include "binmaps.inc" #include "runtimechecks.inc" + +#if ONLY_MSPACES +#include "threaded.inc" +#endif + #include "init.inc" #include "debuglib.inc" #include "statistics.inc" @@ -87,7 +84,7 @@ /* -------------------------- System allocation -------------------------- */ -/* Get memory from system using MORECORE or MMAP */ +/* Get memory from system */ static void* sys_alloc(mstate m, size_t nb) { char* tbase = CMFAIL; size_t tsize = 0; @@ -112,90 +109,7 @@ static void* sys_alloc(mstate m, size_t nb) { return 0; } - /* - Try getting memory in any of three ways (in most-preferred to - least-preferred order): - 1. A call to MORECORE that can normally contiguously extend memory. - (disabled if not MORECORE_CONTIGUOUS or not HAVE_MORECORE or - or main space is mmapped or a previous contiguous call failed) - 2. A call to MMAP new space (disabled if not HAVE_MMAP). - Note that under the default settings, if MORECORE is unable to - fulfill a request, and HAVE_MMAP is true, then mmap is - used as a noncontiguous system allocator. This is a useful backup - strategy for systems with holes in address spaces -- in this case - sbrk cannot contiguously expand the heap, but mmap may be able to - find space. - 3. A call to MORECORE that cannot usually contiguously extend memory. - (disabled if not HAVE_MORECORE) - - In all cases, we need to request enough bytes from system to ensure - we can malloc nb bytes upon success, so pad with enough space for - top_foot, plus alignment-pad to make sure we don't lose bytes if - not on boundary, and round this up to a granularity unit. - */ - - if (MORECORE_CONTIGUOUS && !use_noncontiguous(m)) { - char* br = CMFAIL; - size_t ssize = asize; /* sbrk call size */ - msegmentptr ss = (m->top == 0)? 0 : segment_holding(m, (char*)m->top); - ACQUIRE_MALLOC_GLOBAL_LOCK(); - - if (ss == 0) { /* First time through or recovery */ - char* base = (char*)CALL_MORECORE(0); - if (base != CMFAIL) { - size_t fp; - /* Adjust to end on a page boundary */ - if (!is_page_aligned(base)) - ssize += (page_align((size_t)base) - (size_t)base); - fp = m->footprint + ssize; /* recheck limits */ - if (ssize > nb && ssize < HALF_MAX_SIZE_T && - (m->footprint_limit == 0 || - (fp > m->footprint && fp <= m->footprint_limit)) && - (br = (char*)(CALL_MORECORE(ssize))) == base) { - tbase = base; - tsize = ssize; - } - } - } - else { - /* Subtract out existing available top space from MORECORE request. */ - ssize = granularity_align(nb - m->topsize + SYS_ALLOC_PADDING); - /* Use mem here only if it did continuously extend old space */ - if (ssize < HALF_MAX_SIZE_T && - (br = (char*)(CALL_MORECORE(ssize))) == ss->base+ss->size) { - tbase = br; - tsize = ssize; - } - } - - if (tbase == CMFAIL) { /* Cope with partial failure */ - if (br != CMFAIL) { /* Try to use/extend the space we did get */ - if (ssize < HALF_MAX_SIZE_T && - ssize < nb + SYS_ALLOC_PADDING) { - size_t esize = granularity_align(nb + SYS_ALLOC_PADDING - ssize); - if (esize < HALF_MAX_SIZE_T) { - char* end = (char*)CALL_MORECORE(esize); - if (end != CMFAIL) - ssize += esize; - else { /* Can't use; try to release */ - (void) CALL_MORECORE(-ssize); - br = CMFAIL; - } - } - } - } - if (br != CMFAIL) { /* Use the space we did get */ - tbase = br; - tsize = ssize; - } - else - disable_contiguous(m); /* Don't try contiguous path in the future */ - } - - RELEASE_MALLOC_GLOBAL_LOCK(); - } - - if (HAVE_MMAP && tbase == CMFAIL) { /* Try MMAP */ + if (tbase == CMFAIL) { /* Try MMAP */ char* mp = dlmalloc_requires_more_vespene_gas(asize); if (mp != CMFAIL) { tbase = mp; @@ -204,24 +118,6 @@ static void* sys_alloc(mstate m, size_t nb) { } } - if (HAVE_MORECORE && tbase == CMFAIL) { /* Try noncontiguous MORECORE */ - if (asize < HALF_MAX_SIZE_T) { - char* br = CMFAIL; - char* end = CMFAIL; - ACQUIRE_MALLOC_GLOBAL_LOCK(); - br = (char*)(CALL_MORECORE(asize)); - end = (char*)(CALL_MORECORE(0)); - RELEASE_MALLOC_GLOBAL_LOCK(); - if (br != CMFAIL && end != CMFAIL && br < end) { - size_t ssize = end - br; - if (ssize > nb + TOP_FOOT_SIZE) { - tbase = br; - tsize = ssize; - } - } - } - } - if (tbase != CMFAIL) { if ((m->footprint += tsize) > m->max_footprint) @@ -361,8 +257,7 @@ static int sys_trim(mstate m, size_t pad) { if (!is_extern_segment(sp)) { if (is_mmapped_segment(sp)) { - if (HAVE_MMAP && - sp->size >= extra && + if (sp->size >= extra && !has_segment_link(m, sp)) { /* can't shrink if pinned */ size_t newsize = sp->size - extra; (void)newsize; /* placate people compiling -Wunused-variable */ @@ -373,22 +268,6 @@ static int sys_trim(mstate m, size_t pad) { } } } - else if (HAVE_MORECORE) { - if (extra >= HALF_MAX_SIZE_T) /* Avoid wrapping negative */ - extra = (HALF_MAX_SIZE_T) + SIZE_T_ONE - unit; - ACQUIRE_MALLOC_GLOBAL_LOCK(); - { - /* Make sure end of memory is where we last set it. */ - char* old_br = (char*)(CALL_MORECORE(0)); - if (old_br == sp->base + sp->size) { - char* rel_br = (char*)(CALL_MORECORE(-extra)); - char* new_br = (char*)(CALL_MORECORE(0)); - if (rel_br != CMFAIL && new_br < old_br) - released = old_br - new_br; - } - } - RELEASE_MALLOC_GLOBAL_LOCK(); - } } if (released != 0) { @@ -400,8 +279,7 @@ static int sys_trim(mstate m, size_t pad) { } /* Unmap any unused mmapped segments */ - if (HAVE_MMAP) - released += release_unused_segments(m); + released += release_unused_segments(m); /* On failure, disable autotrim to avoid repeated failed future calls */ if (released == 0 && m->topsize > m->trim_check) @@ -1263,12 +1141,15 @@ void* dlrealloc_single(void* oldmem, size_t bytes) { #endif /* FOOTERS */ if (!PREACTION(m)) { mchunkptr newp = try_realloc_chunk(m, oldp, nb, MREMAP_MAYMOVE); - POSTACTION(m); if (newp != 0) { + /* [jart] fix realloc MT bug in DEBUG mode + https://github.com/intel/linux-sgx/issues/534 */ check_inuse_chunk(m, newp); + POSTACTION(m); mem = chunk2mem(newp); } else { + POSTACTION(m); mem = internal_malloc(m, bytes); if (mem != 0) { size_t oc = chunksize(oldp) - overhead_for(oldp); @@ -1301,11 +1182,13 @@ void* dlrealloc_in_place(void* oldmem, size_t bytes) { #endif /* FOOTERS */ if (!PREACTION(m)) { mchunkptr newp = try_realloc_chunk(m, oldp, nb, 0); - POSTACTION(m); if (newp == oldp) { + /* [jart] fix realloc MT bug in DEBUG mode + https://github.com/intel/linux-sgx/issues/534 */ check_inuse_chunk(m, newp); mem = oldmem; } + POSTACTION(m); } } } @@ -1319,13 +1202,6 @@ void* dlmemalign_single(size_t alignment, size_t bytes) { return internal_memalign(gm, alignment, bytes); } -#if USE_LOCKS -void dlmalloc_atfork(void) { - bzero(&gm->mutex, sizeof(gm->mutex)); - bzero(&malloc_global_mutex, sizeof(malloc_global_mutex)); -} -#endif - void** dlindependent_calloc(size_t n_elements, size_t elem_size, void* chunks[]) { size_t sz = elem_size; /* serves as 1-element array */ diff --git a/third_party/dlmalloc/dlmalloc.h b/third_party/dlmalloc/dlmalloc.h index edb86f27a..5bbb9a179 100644 --- a/third_party/dlmalloc/dlmalloc.h +++ b/third_party/dlmalloc/dlmalloc.h @@ -9,7 +9,6 @@ #define dlmallinfo __dlmallinfo #define dlmalloc __dlmalloc #define dlmalloc_abort __dlmalloc_abort -#define dlmalloc_atfork __dlmalloc_atfork #define dlmalloc_footprint __dlmalloc_footprint #define dlmalloc_footprint_limit __dlmalloc_footprint_limit #define dlmalloc_inspect_all __dlmalloc_inspect_all @@ -527,7 +526,10 @@ void mspace_inspect_all(mspace msp, void (*handler)(void*, void*, size_t, void*), void* arg); -void dlmalloc_atfork(void); +void dlmalloc_pre_fork(void) libcesque; +void dlmalloc_post_fork_parent(void) libcesque; +void dlmalloc_post_fork_child(void) libcesque; + void dlmalloc_abort(void) relegated wontreturn; COSMOPOLITAN_C_END_ diff --git a/third_party/dlmalloc/init.inc b/third_party/dlmalloc/init.inc index 682b50408..ac7ce8edf 100644 --- a/third_party/dlmalloc/init.inc +++ b/third_party/dlmalloc/init.inc @@ -1,68 +1,51 @@ #include "libc/sysv/consts/auxv.h" #include "libc/runtime/runtime.h" +#include "libc/nexgen32e/rdtsc.h" #include "libc/runtime/runtime.h" -/* ---------------------------- setting mparams -------------------------- */ - -#if LOCK_AT_FORK +void dlmalloc_pre_fork(void) { #if ONLY_MSPACES - -static void dlmalloc_pre_fork(void) { mstate h; - for (unsigned i = 0; i < ARRAYLEN(g_heaps); ++i) + for (unsigned i = ARRAYLEN(g_heaps); i--;) if ((h = atomic_load_explicit(&g_heaps[i], memory_order_acquire))) ACQUIRE_LOCK(&h->mutex); +#else + ACQUIRE_LOCK(&(gm)->mutex); +#endif } -static void dlmalloc_post_fork_parent(void) { +void dlmalloc_post_fork_parent(void) { +#if ONLY_MSPACES mstate h; for (unsigned i = 0; i < ARRAYLEN(g_heaps); ++i) if ((h = atomic_load_explicit(&g_heaps[i], memory_order_acquire))) RELEASE_LOCK(&h->mutex); +#else + RELEASE_LOCK(&(gm)->mutex); +#endif } -static void dlmalloc_post_fork_child(void) { +void dlmalloc_post_fork_child(void) { +#if ONLY_MSPACES mstate h; for (unsigned i = 0; i < ARRAYLEN(g_heaps); ++i) if ((h = atomic_load_explicit(&g_heaps[i], memory_order_acquire))) - (void)INITIAL_LOCK(&h->mutex); -} - + REFRESH_LOCK(&h->mutex); #else -static void dlmalloc_pre_fork(void) { ACQUIRE_LOCK(&(gm)->mutex); } -static void dlmalloc_post_fork_parent(void) { RELEASE_LOCK(&(gm)->mutex); } -static void dlmalloc_post_fork_child(void) { (void)INITIAL_LOCK(&(gm)->mutex); } -#endif /* ONLY_MSPACES */ -#endif /* LOCK_AT_FORK */ + REFRESH_LOCK(&(gm)->mutex); +#endif +} /* Initialize mparams */ __attribute__((__constructor__(49))) int init_mparams(void) { -#ifdef NEED_GLOBAL_LOCK_INIT - if (malloc_global_mutex_status <= 0) - init_malloc_global_mutex(); -#endif - // ACQUIRE_MALLOC_GLOBAL_LOCK(); if (mparams.magic == 0) { size_t magic; size_t psize; size_t gsize; -#if defined(__COSMOPOLITAN__) - psize = getpagesize(); + psize = __pagesize; gsize = DEFAULT_GRANULARITY ? DEFAULT_GRANULARITY : psize; -#elif !defined(WIN32) - psize = malloc_getpagesize; - gsize = ((DEFAULT_GRANULARITY != 0)? DEFAULT_GRANULARITY : psize); -#else /* WIN32 */ - { - SYSTEM_INFO system_info; - GetSystemInfo(&system_info); - psize = system_info.dwPageSize; - gsize = ((DEFAULT_GRANULARITY != 0)? - DEFAULT_GRANULARITY : system_info.dwAllocationGranularity); - } -#endif /* WIN32 */ /* Sanity-check configuration: size_t must be unsigned and as wide as pointer type. @@ -83,11 +66,7 @@ __attribute__((__constructor__(49))) int init_mparams(void) { mparams.page_size = psize; mparams.mmap_threshold = DEFAULT_MMAP_THRESHOLD; mparams.trim_threshold = DEFAULT_TRIM_THRESHOLD; -#if MORECORE_CONTIGUOUS - mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT; -#else /* MORECORE_CONTIGUOUS */ mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT|USE_NONCONTIGUOUS_BIT; -#endif /* MORECORE_CONTIGUOUS */ #if !ONLY_MSPACES /* Set up lock for main malloc area */ @@ -95,12 +74,6 @@ __attribute__((__constructor__(49))) int init_mparams(void) { (void)INITIAL_LOCK(&gm->mutex); #endif -#if LOCK_AT_FORK - pthread_atfork(&dlmalloc_pre_fork, - &dlmalloc_post_fork_parent, - &dlmalloc_post_fork_child); -#endif - { #if USE_DEV_RANDOM int fd; @@ -113,7 +86,7 @@ __attribute__((__constructor__(49))) int init_mparams(void) { } else #endif /* USE_DEV_RANDOM */ - magic = (size_t)(_rand64() ^ (size_t)0x55555555U); + magic = (size_t)(rdtsc() ^ (size_t)0x55555555U); magic |= (size_t)8U; /* ensure nonzero */ magic &= ~(size_t)7U; /* improve chances of fault for bad values */ /* Until memory modes commonly available, use volatile-write */ @@ -121,8 +94,6 @@ __attribute__((__constructor__(49))) int init_mparams(void) { } } - // RELEASE_MALLOC_GLOBAL_LOCK(); - #if ONLY_MSPACES threaded_dlmalloc(); #endif diff --git a/third_party/dlmalloc/locks.inc b/third_party/dlmalloc/locks.inc index 4e6c0198a..ea962c778 100644 --- a/third_party/dlmalloc/locks.inc +++ b/third_party/dlmalloc/locks.inc @@ -1,3 +1,7 @@ +#include "libc/cosmo.h" +#include "libc/intrin/kprintf.h" +#include "libc/intrin/maps.h" +#include "libc/thread/thread.h" /* --------------------------- Lock preliminaries ------------------------ */ @@ -33,11 +37,20 @@ #define MLOCK_T atomic_uint +static int malloc_inlk(MLOCK_T *lk) { + atomic_store_explicit(lk, 0, memory_order_relaxed); + return 0; +} + static int malloc_wipe(MLOCK_T *lk) { atomic_store_explicit(lk, 0, memory_order_relaxed); return 0; } +static int malloc_kilk(MLOCK_T *lk) { + return 0; +} + static int malloc_lock(MLOCK_T *lk) { for (;;) { if (!atomic_exchange_explicit(lk, 1, memory_order_acquire)) @@ -49,40 +62,69 @@ static int malloc_lock(MLOCK_T *lk) { return 0; } -static int malloc_unlock(MLOCK_T *lk) { +static int malloc_unlk(MLOCK_T *lk) { atomic_store_explicit(lk, 0, memory_order_release); return 0; } #else -#define MLOCK_T nsync_mu +#define MLOCK_T struct MallocLock -static int malloc_wipe(MLOCK_T *lk) { +struct MallocLock { +#if DEBUG + void *edges; +#endif + nsync_mu mu; +}; + +static int malloc_inlk(MLOCK_T *lk) { bzero(lk, sizeof(*lk)); return 0; } -static int malloc_lock(MLOCK_T *lk) { - nsync_mu_lock(lk); +static int malloc_wipe(MLOCK_T *lk) { + bzero(&lk->mu, sizeof(lk->mu)); return 0; } -static int malloc_unlock(MLOCK_T *lk) { - nsync_mu_unlock(lk); +static int malloc_kilk(MLOCK_T *lk) { + return 0; +} + +static int malloc_lock(MLOCK_T *lk) { +#if DEBUG + __deadlock_check(lk, 0); +#endif + nsync_mu_lock(&lk->mu); +#if DEBUG + __deadlock_record(lk, 0); + __deadlock_track(lk, 0); +#endif + return 0; +} + +static int malloc_unlk(MLOCK_T *lk) { +#if DEBUG + if (__deadlock_tracked(lk) == 0) { + kprintf("error: unlock malloc mutex not owned by caller: %t\n", lk); + DebugBreak(); + } +#endif + nsync_mu_unlock(&lk->mu); +#if DEBUG + __deadlock_untrack(lk); +#endif return 0; } #endif #define ACQUIRE_LOCK(lk) malloc_lock(lk) -#define RELEASE_LOCK(lk) malloc_unlock(lk) -#define INITIAL_LOCK(lk) malloc_wipe(lk) -#define DESTROY_LOCK(lk) malloc_wipe(lk) -#define ACQUIRE_MALLOC_GLOBAL_LOCK() ACQUIRE_LOCK(&malloc_global_mutex); -#define RELEASE_MALLOC_GLOBAL_LOCK() RELEASE_LOCK(&malloc_global_mutex); - -static MLOCK_T malloc_global_mutex; +#define RELEASE_LOCK(lk) malloc_unlk(lk) +#define INITIAL_LOCK(lk) malloc_inlk(lk) +#define REFRESH_LOCK(lk) malloc_wipe(lk) +#define DESTROY_LOCK(lk) malloc_kilk(lk) #define USE_LOCK_BIT (2U) diff --git a/third_party/dlmalloc/mspaces.inc b/third_party/dlmalloc/mspaces.inc index 1f048d0eb..d17d96549 100644 --- a/third_party/dlmalloc/mspaces.inc +++ b/third_party/dlmalloc/mspaces.inc @@ -368,12 +368,15 @@ void* mspace_realloc(mspace msp, void* oldmem, size_t bytes) { #endif /* FOOTERS */ if (!PREACTION(m)) { mchunkptr newp = try_realloc_chunk(m, oldp, nb, 1); - POSTACTION(m); if (newp != 0) { + /* [jart] fix realloc MT bug in DEBUG mode + https://github.com/intel/linux-sgx/issues/534 */ check_inuse_chunk(m, newp); + POSTACTION(m); mem = chunk2mem(newp); } else { + POSTACTION(m); mem = mspace_malloc(m, bytes); if (mem != 0) { size_t oc = chunksize(oldp) - overhead_for(oldp); @@ -407,11 +410,13 @@ void* mspace_realloc_in_place(mspace msp, void* oldmem, size_t bytes) { #endif /* FOOTERS */ if (!PREACTION(m)) { mchunkptr newp = try_realloc_chunk(m, oldp, nb, 0); - POSTACTION(m); if (newp == oldp) { + /* [jart] fix realloc_in_place MT bug in DEBUG mode + https://github.com/intel/linux-sgx/issues/534 */ check_inuse_chunk(m, newp); mem = oldmem; } + POSTACTION(m); } } } diff --git a/third_party/dlmalloc/platform.inc b/third_party/dlmalloc/platform.inc index 8fab2e29e..5385a7f88 100644 --- a/third_party/dlmalloc/platform.inc +++ b/third_party/dlmalloc/platform.inc @@ -75,9 +75,6 @@ #ifndef MALLOC_INSPECT_ALL #define MALLOC_INSPECT_ALL 0 #endif /* MALLOC_INSPECT_ALL */ -#ifndef HAVE_MMAP -#define HAVE_MMAP 1 -#endif /* HAVE_MMAP */ #ifndef MMAP_CLEARS #define MMAP_CLEARS 1 #endif /* MMAP_CLEARS */ @@ -92,48 +89,17 @@ #ifndef MALLOC_FAILURE_ACTION #define MALLOC_FAILURE_ACTION errno = ENOMEM; #endif /* MALLOC_FAILURE_ACTION */ -#ifndef HAVE_MORECORE -#if ONLY_MSPACES -#define HAVE_MORECORE 0 -#else /* ONLY_MSPACES */ -#define HAVE_MORECORE 1 -#endif /* ONLY_MSPACES */ -#endif /* HAVE_MORECORE */ -#if !HAVE_MORECORE -#define MORECORE_CONTIGUOUS 0 -#else /* !HAVE_MORECORE */ -#define MORECORE_DEFAULT sbrk -#ifndef MORECORE_CONTIGUOUS -#define MORECORE_CONTIGUOUS 1 -#endif /* MORECORE_CONTIGUOUS */ -#endif /* HAVE_MORECORE */ #ifndef DEFAULT_GRANULARITY -#if (MORECORE_CONTIGUOUS || defined(WIN32)) -#define DEFAULT_GRANULARITY (0) /* 0 means to compute in init_mparams */ -#else /* MORECORE_CONTIGUOUS */ #define DEFAULT_GRANULARITY ((size_t)64U * (size_t)1024U) -#endif /* MORECORE_CONTIGUOUS */ #endif /* DEFAULT_GRANULARITY */ #ifndef DEFAULT_TRIM_THRESHOLD -#ifndef MORECORE_CANNOT_TRIM #define DEFAULT_TRIM_THRESHOLD ((size_t)2U * (size_t)1024U * (size_t)1024U) -#else /* MORECORE_CANNOT_TRIM */ -#define DEFAULT_TRIM_THRESHOLD MAX_SIZE_T -#endif /* MORECORE_CANNOT_TRIM */ #endif /* DEFAULT_TRIM_THRESHOLD */ #ifndef DEFAULT_MMAP_THRESHOLD -#if HAVE_MMAP #define DEFAULT_MMAP_THRESHOLD ((size_t)256U * (size_t)1024U) -#else /* HAVE_MMAP */ -#define DEFAULT_MMAP_THRESHOLD MAX_SIZE_T -#endif /* HAVE_MMAP */ #endif /* DEFAULT_MMAP_THRESHOLD */ #ifndef MAX_RELEASE_CHECK_RATE -#if HAVE_MMAP #define MAX_RELEASE_CHECK_RATE 4095 -#else -#define MAX_RELEASE_CHECK_RATE MAX_SIZE_T -#endif /* HAVE_MMAP */ #endif /* MAX_RELEASE_CHECK_RATE */ #ifndef USE_BUILTIN_FFS #define USE_BUILTIN_FFS 0 @@ -185,165 +151,6 @@ ======================================================================== */ -/* #include "malloc.h" */ - -/*------------------------------ internal #includes ---------------------- */ - -#ifdef _MSC_VER -#pragma warning( disable : 4146 ) /* no "unsigned" warnings */ -#endif /* _MSC_VER */ -#if !NO_MALLOC_STATS -#endif /* NO_MALLOC_STATS */ -#ifndef LACKS_ERRNO_H -#include /* for MALLOC_FAILURE_ACTION */ -#endif /* LACKS_ERRNO_H */ -#ifdef DEBUG -#if ABORT_ON_ASSERT_FAILURE -#endif /* ABORT_ON_ASSERT_FAILURE */ -#else /* DEBUG */ -#ifndef assert -#define assert(x) -#endif -#define DEBUG 0 -#endif /* DEBUG */ -#if !defined(WIN32) && !defined(LACKS_TIME_H) -#include /* for magic initialization */ -#endif /* WIN32 */ -#ifndef LACKS_STDLIB_H -#include /* for abort() */ -#endif /* LACKS_STDLIB_H */ -#ifndef LACKS_STRING_H -#include /* for memset etc */ -#endif /* LACKS_STRING_H */ -#if USE_BUILTIN_FFS -#ifndef LACKS_STRINGS_H -#include /* for ffs */ -#endif /* LACKS_STRINGS_H */ -#endif /* USE_BUILTIN_FFS */ -#if HAVE_MMAP -#ifndef LACKS_SYS_MMAN_H -/* On some versions of linux, mremap decl in mman.h needs __USE_GNU set */ -#if (defined(linux) && !defined(__USE_GNU)) -#define __USE_GNU 1 -#include /* for mmap */ -#undef __USE_GNU -#else -#include /* for mmap */ -#endif /* linux */ -#endif /* LACKS_SYS_MMAN_H */ -#ifndef LACKS_FCNTL_H -#include -#endif /* LACKS_FCNTL_H */ -#endif /* HAVE_MMAP */ -#ifndef LACKS_UNISTD_H -#include /* for sbrk, sysconf */ -#else /* LACKS_UNISTD_H */ -#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) && !defined(__COSMOPOLITAN__) -extern void* sbrk(ptrdiff_t); -#endif /* FreeBSD etc */ -#endif /* LACKS_UNISTD_H */ - -/* Declarations for locking */ -#if USE_LOCKS -#ifndef WIN32 -#if defined (__SVR4) && defined (__sun) /* solaris */ -#elif !defined(LACKS_SCHED_H) -#endif /* solaris or LACKS_SCHED_H */ -#if (defined(USE_RECURSIVE_LOCKS) && USE_RECURSIVE_LOCKS != 0) || !USE_SPIN_LOCKS -#endif /* USE_RECURSIVE_LOCKS ... */ -#elif defined(_MSC_VER) -#ifndef _M_AMD64 -/* These are already defined on AMD64 builds */ -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ -LONG __cdecl _InterlockedCompareExchange(LONG volatile *Dest, LONG Exchange, LONG Comp); -LONG __cdecl _InterlockedExchange(LONG volatile *Target, LONG Value); -#ifdef __cplusplus -} -#endif /* __cplusplus */ -#endif /* _M_AMD64 */ -#pragma intrinsic (_InterlockedCompareExchange) -#pragma intrinsic (_InterlockedExchange) -#define interlockedcompareexchange _InterlockedCompareExchange -#define interlockedexchange _InterlockedExchange -#elif defined(WIN32) && defined(__GNUC__) -#define interlockedcompareexchange(a, b, c) __sync_val_compare_and_swap(a, c, b) -#define interlockedexchange __sync_lock_test_and_set -#endif /* Win32 */ -#else /* USE_LOCKS */ -#endif /* USE_LOCKS */ - -#ifndef LOCK_AT_FORK -#define LOCK_AT_FORK 0 -#endif - -/* Declarations for bit scanning on win32 */ -#if defined(_MSC_VER) && _MSC_VER>=1300 -#ifndef BitScanForward /* Try to avoid pulling in WinNT.h */ -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ -unsigned char _BitScanForward(unsigned long *index, unsigned long mask); -unsigned char _BitScanReverse(unsigned long *index, unsigned long mask); -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#define BitScanForward _BitScanForward -#define BitScanReverse _BitScanReverse -#pragma intrinsic(_BitScanForward) -#pragma intrinsic(_BitScanReverse) -#endif /* BitScanForward */ -#endif /* defined(_MSC_VER) && _MSC_VER>=1300 */ - -#ifndef WIN32 -#ifndef malloc_getpagesize -# ifdef _SC_PAGESIZE /* some SVR4 systems omit an underscore */ -# ifndef _SC_PAGE_SIZE -# define _SC_PAGE_SIZE _SC_PAGESIZE -# endif -# endif -# ifdef _SC_PAGE_SIZE -# define malloc_getpagesize 4096 /*sysconf(_SC_PAGE_SIZE)*/ -# else -# if defined(BSD) || defined(DGUX) || defined(HAVE_GETPAGESIZE) - extern size_t getpagesize(); -# define malloc_getpagesize getpagesize() -# else -# ifdef WIN32 /* use supplied emulation of getpagesize */ -# define malloc_getpagesize getpagesize() -# else -# ifndef LACKS_SYS_PARAM_H -# include -# endif -# ifdef EXEC_PAGESIZE -# define malloc_getpagesize EXEC_PAGESIZE -# else -# ifdef NBPG -# ifndef CLSIZE -# define malloc_getpagesize NBPG -# else -# define malloc_getpagesize (NBPG * CLSIZE) -# endif -# else -# ifdef NBPC -# define malloc_getpagesize NBPC -# else -# ifdef PAGESIZE -# define malloc_getpagesize PAGESIZE -# else /* just guess */ -# define malloc_getpagesize ((size_t)4096U) -# endif -# endif -# endif -# endif -# endif -# endif -# endif -#endif -#endif - /* ------------------- size_t and alignment properties -------------------- */ /* The byte and bit size of a size_t */ @@ -374,141 +181,53 @@ unsigned char _BitScanReverse(unsigned long *index, unsigned long mask); /* -------------------------- MMAP preliminaries ------------------------- */ -/* - If HAVE_MORECORE or HAVE_MMAP are false, we just define calls and - checks to fail so compiler optimizer can delete code rather than - using so many "#if"s. -*/ - - /* MORECORE and MMAP must return MFAIL on failure */ #define MFAIL NULL #define CMFAIL ((char*)(MFAIL)) /* defined for convenience */ -#if HAVE_MMAP - -#ifndef WIN32 #define MUNMAP_DEFAULT(a, s) munmap((a), (s)) #define MMAP_PROT (PROT_READ|PROT_WRITE) -#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) -#define MAP_ANONYMOUS MAP_ANON -#endif /* MAP_ANON */ -#ifdef MAP_ANONYMOUS #define MMAP_FLAGS (MAP_PRIVATE|MAP_ANONYMOUS) #define MMAP_DEFAULT(s) _mapanon(s) -#else /* MAP_ANONYMOUS */ -/* - Nearly all versions of mmap support MAP_ANONYMOUS, so the following - is unlikely to be needed, but is supplied just in case. -*/ -#define MMAP_FLAGS (MAP_PRIVATE) -static int dev_zero_fd = -1; /* Cached file descriptor for /dev/zero. */ -#define MMAP_DEFAULT(s) ((dev_zero_fd < 0) ? \ - (dev_zero_fd = open("/dev/zero", O_RDWR), \ - mmap_no(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0)) : \ - mmap_no(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0)) -#endif /* MAP_ANONYMOUS */ - #define DIRECT_MMAP_DEFAULT(s) MMAP_DEFAULT(s) -#else /* WIN32 */ - -/* Win32 MMAP via VirtualAlloc */ -FORCEINLINE void* win32mmap(size_t size) { - void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); - return (ptr != 0)? ptr: MFAIL; -} - -/* For direct MMAP, use MEM_TOP_DOWN to minimize interference */ -FORCEINLINE void* win32direct_mmap(size_t size) { - void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN, - PAGE_READWRITE); - return (ptr != 0)? ptr: MFAIL; -} - -/* This function supports releasing coalesed segments */ -FORCEINLINE int win32munmap(void* ptr, size_t size) { - MEMORY_BASIC_INFORMATION minfo; - char* cptr = (char*)ptr; - while (size) { - if (VirtualQuery(cptr, &minfo, sizeof(minfo)) == 0) - return -1; - if (minfo.BaseAddress != cptr || minfo.AllocationBase != cptr || - minfo.State != MEM_COMMIT || minfo.RegionSize > size) - return -1; - if (VirtualFree(cptr, 0, MEM_RELEASE) == 0) - return -1; - cptr += minfo.RegionSize; - size -= minfo.RegionSize; - } - return 0; -} - -#define MMAP_DEFAULT(s) win32mmap(s) -#define MUNMAP_DEFAULT(a, s) win32munmap((a), (s)) -#define DIRECT_MMAP_DEFAULT(s) win32direct_mmap(s) -#endif /* WIN32 */ -#endif /* HAVE_MMAP */ - #if HAVE_MREMAP #ifndef WIN32 #define MREMAP_DEFAULT(addr, osz, nsz, mv) mremap((addr), (osz), (nsz), (mv)) #endif /* WIN32 */ #endif /* HAVE_MREMAP */ -/** - * Define CALL_MORECORE - */ -#if HAVE_MORECORE - #ifdef MORECORE - #define CALL_MORECORE(S) MORECORE(S) - #else /* MORECORE */ - #define CALL_MORECORE(S) MORECORE_DEFAULT(S) - #endif /* MORECORE */ -#else /* HAVE_MORECORE */ - #define CALL_MORECORE(S) MFAIL -#endif /* HAVE_MORECORE */ - /** * Define CALL_MMAP/CALL_MUNMAP/CALL_DIRECT_MMAP */ -#if HAVE_MMAP - #define USE_MMAP_BIT (SIZE_T_ONE) +#define USE_MMAP_BIT (SIZE_T_ONE) - #ifdef MMAP - #define CALL_MMAP(s) MMAP(s) - #else /* MMAP */ - #define CALL_MMAP(s) MMAP_DEFAULT(s) - #endif /* MMAP */ - #ifdef MUNMAP - #define CALL_MUNMAP(a, s) MUNMAP((a), (s)) - #else /* MUNMAP */ - #define CALL_MUNMAP(a, s) MUNMAP_DEFAULT((a), (s)) - #endif /* MUNMAP */ - #ifdef DIRECT_MMAP - #define CALL_DIRECT_MMAP(s) DIRECT_MMAP(s) - #else /* DIRECT_MMAP */ - #define CALL_DIRECT_MMAP(s) DIRECT_MMAP_DEFAULT(s) - #endif /* DIRECT_MMAP */ -#else /* HAVE_MMAP */ - #define USE_MMAP_BIT (SIZE_T_ZERO) +#ifdef MMAP +#define CALL_MMAP(s) MMAP(s) +#else /* MMAP */ +#define CALL_MMAP(s) MMAP_DEFAULT(s) +#endif /* MMAP */ - #define MMAP(s) MFAIL - #define MUNMAP(a, s) (-1) - #define DIRECT_MMAP(s) MFAIL - #define CALL_DIRECT_MMAP(s) DIRECT_MMAP(s) - #define CALL_MMAP(s) MMAP(s) - #define CALL_MUNMAP(a, s) MUNMAP((a), (s)) -#endif /* HAVE_MMAP */ +#ifdef MUNMAP +#define CALL_MUNMAP(a, s) MUNMAP((a), (s)) +#else /* MUNMAP */ +#define CALL_MUNMAP(a, s) MUNMAP_DEFAULT((a), (s)) +#endif /* MUNMAP */ + +#ifdef DIRECT_MMAP +#define CALL_DIRECT_MMAP(s) DIRECT_MMAP(s) +#else /* DIRECT_MMAP */ +#define CALL_DIRECT_MMAP(s) DIRECT_MMAP_DEFAULT(s) +#endif /* DIRECT_MMAP */ /** * Define CALL_MREMAP */ -#if HAVE_MMAP && HAVE_MREMAP +#if HAVE_MREMAP #define CALL_MREMAP(addr, osz, nsz, mv) ({ int olderr = errno; void *res = mremap((addr), (osz), (nsz), (mv)); if (res == MAP_FAILED) errno = olderr; res; }) -#else /* HAVE_MMAP && HAVE_MREMAP */ +#else /* HAVE_MREMAP */ #define CALL_MREMAP(addr, osz, nsz, mv) MAP_FAILED -#endif /* HAVE_MMAP && HAVE_MREMAP */ +#endif /* HAVE_MREMAP */ /* mstate bit set if continguous morecore disabled or failed */ #define USE_NONCONTIGUOUS_BIT (4U) diff --git a/third_party/dlmalloc/runtimechecks.inc b/third_party/dlmalloc/runtimechecks.inc index df3fd226c..dc86de808 100644 --- a/third_party/dlmalloc/runtimechecks.inc +++ b/third_party/dlmalloc/runtimechecks.inc @@ -28,7 +28,7 @@ */ #if !INSECURE -/* Check if address a is at least as high as any from MORECORE or MMAP */ +/* Check if address a is at least as high as any from MMAP */ #define ok_address(M, a) ((char*)(a) >= (M)->least_addr) /* Check if address of next chunk n is higher than base chunk p */ #define ok_next(p, n) ((char*)(p) < (char*)(n)) diff --git a/third_party/dlmalloc/system.inc b/third_party/dlmalloc/system.inc index b8fc6ab79..443fdfac9 100644 --- a/third_party/dlmalloc/system.inc +++ b/third_party/dlmalloc/system.inc @@ -13,11 +13,7 @@ #define use_mmap(M) ((M)->mflags & USE_MMAP_BIT) #define enable_mmap(M) ((M)->mflags |= USE_MMAP_BIT) -#if HAVE_MMAP #define disable_mmap(M) ((M)->mflags &= ~USE_MMAP_BIT) -#else -#define disable_mmap(M) -#endif #define use_noncontiguous(M) ((M)->mflags & USE_NONCONTIGUOUS_BIT) #define disable_contiguous(M) ((M)->mflags |= USE_NONCONTIGUOUS_BIT) @@ -78,11 +74,7 @@ static int has_segment_link(mstate m, msegmentptr ss) { } } -#ifndef MORECORE_CANNOT_TRIM #define should_trim(M,s) ((s) > (M)->trim_check) -#else /* MORECORE_CANNOT_TRIM */ -#define should_trim(M,s) (0) -#endif /* MORECORE_CANNOT_TRIM */ /* TOP_FOOT_SIZE is padding at the end of a segment, including space diff --git a/third_party/dlmalloc/threaded.inc b/third_party/dlmalloc/threaded.inc index e8768dbc3..3dbfb5b35 100644 --- a/third_party/dlmalloc/threaded.inc +++ b/third_party/dlmalloc/threaded.inc @@ -61,8 +61,23 @@ int dlmalloc_trim(size_t pad) { } size_t dlbulk_free(void *array[], size_t nelem) { - for (size_t i = 0; i < nelem; ++i) - mspace_free(0, array[i]); + size_t j = 0; + mstate msp = (mstate)-1; + for (size_t i = 0; i < nelem; ++i) { + mstate next; + if (array[i]) { + next = get_mstate_for(mem2chunk(array[i])); + if (next != msp) { + if (j) + mspace_bulk_free(msp, array, j); + msp = next; + j = 0; + } + array[j++] = array[i]; + } + } + if (j) + mspace_bulk_free(msp, array, j); return 0; } diff --git a/third_party/gdtoa/lock.c b/third_party/gdtoa/lock.c new file mode 100644 index 000000000..1e5cc36de --- /dev/null +++ b/third_party/gdtoa/lock.c @@ -0,0 +1,72 @@ +/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│ +│ vi: set noet ft=c ts=8 sw=8 fenc=utf-8 :vi │ +╚──────────────────────────────────────────────────────────────────────────────╝ +│ │ +│ The author of this software is David M. Gay. │ +│ Please send bug reports to David M. Gay │ +│ or Justine Tunney │ +│ │ +│ Copyright (C) 1998, 1999 by Lucent Technologies │ +│ All Rights Reserved │ +│ │ +│ Permission to use, copy, modify, and distribute this software and │ +│ its documentation for any purpose and without fee is hereby │ +│ granted, provided that the above copyright notice appear in all │ +│ copies and that both that the copyright notice and this │ +│ permission notice and warranty disclaimer appear in supporting │ +│ documentation, and that the name of Lucent or any of its entities │ +│ not be used in advertising or publicity pertaining to │ +│ distribution of the software without specific, written prior │ +│ permission. │ +│ │ +│ LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, │ +│ INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. │ +│ IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY │ +│ SPECIAL, 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/thread/posixthread.internal.h" +#include "third_party/gdtoa/lock.h" + +static pthread_mutex_t __gdtoa_lock_obj = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t __gdtoa_lock1_obj = PTHREAD_MUTEX_INITIALIZER; + +void +__gdtoa_lock(void) +{ + _pthread_mutex_lock(&__gdtoa_lock_obj); +} + +void +__gdtoa_unlock(void) +{ + _pthread_mutex_unlock(&__gdtoa_lock_obj); +} + +void +__gdtoa_wipe(void) +{ + _pthread_mutex_wipe_np(&__gdtoa_lock_obj); +} + +void +__gdtoa_lock1(void) +{ + _pthread_mutex_lock(&__gdtoa_lock1_obj); +} + +void +__gdtoa_unlock1(void) +{ + _pthread_mutex_unlock(&__gdtoa_lock1_obj); +} + +void +__gdtoa_wipe1(void) +{ + _pthread_mutex_wipe_np(&__gdtoa_lock1_obj); +} diff --git a/third_party/gdtoa/lock.h b/third_party/gdtoa/lock.h new file mode 100644 index 000000000..71af847aa --- /dev/null +++ b/third_party/gdtoa/lock.h @@ -0,0 +1,15 @@ +#ifndef COSMOPOLITAN_THIRD_PARTY_GDTOA_LOCK_H_ +#define COSMOPOLITAN_THIRD_PARTY_GDTOA_LOCK_H_ +#include "libc/thread/thread.h" +COSMOPOLITAN_C_START_ + +void __gdtoa_lock(void); +void __gdtoa_unlock(void); +void __gdtoa_wipe(void); + +void __gdtoa_lock1(void); +void __gdtoa_unlock1(void); +void __gdtoa_wipe1(void); + +COSMOPOLITAN_C_END_ +#endif /* COSMOPOLITAN_THIRD_PARTY_GDTOA_LOCK_H_ */ diff --git a/third_party/gdtoa/misc.c b/third_party/gdtoa/misc.c index 0ff9afa12..2d3809a9c 100644 --- a/third_party/gdtoa/misc.c +++ b/third_party/gdtoa/misc.c @@ -35,46 +35,9 @@ #include "libc/thread/thread.h" #include "libc/thread/tls.h" #include "third_party/gdtoa/gdtoa.internal.h" +#include "third_party/gdtoa/lock.h" static ThInfo TI0; -static pthread_mutex_t __gdtoa_lock_obj; -static pthread_mutex_t __gdtoa_lock1_obj; - -static void -__gdtoa_lock(void) -{ - pthread_mutex_lock(&__gdtoa_lock_obj); -} - -static void -__gdtoa_unlock(void) -{ - pthread_mutex_unlock(&__gdtoa_lock_obj); -} - -static void -__gdtoa_initlock(void) -{ - pthread_mutex_init(&__gdtoa_lock_obj, 0); -} - -static void -__gdtoa_lock1(void) -{ - pthread_mutex_lock(&__gdtoa_lock1_obj); -} - -static void -__gdtoa_unlock1(void) -{ - pthread_mutex_unlock(&__gdtoa_lock1_obj); -} - -static void -__gdtoa_initlock1(void) -{ - pthread_mutex_init(&__gdtoa_lock1_obj, 0); -} static void __gdtoa_Brelease(Bigint *rv) @@ -88,24 +51,20 @@ static void __gdtoa_Bclear(void) { int i; - __gdtoa_lock(); + __gdtoa_lock1(); for (i = 0; i < ARRAYLEN(TI0.Freelist); ++i) __gdtoa_Brelease(TI0.Freelist[i]); - __gdtoa_lock1(); + __gdtoa_lock(); __gdtoa_Brelease(TI0.P5s); - __gdtoa_unlock1(); - bzero(&TI0, sizeof(TI0)); __gdtoa_unlock(); + bzero(&TI0, sizeof(TI0)); + __gdtoa_unlock1(); } __attribute__((__constructor__(60))) static void __gdtoa_Binit(void) { - __gdtoa_initlock(); - __gdtoa_initlock1(); atexit(__gdtoa_Bclear); - pthread_atfork(__gdtoa_lock1, __gdtoa_unlock1, __gdtoa_initlock1); - pthread_atfork(__gdtoa_lock, __gdtoa_unlock, __gdtoa_initlock); } static ThInfo * diff --git a/third_party/less/BUILD.mk b/third_party/less/BUILD.mk index b183fad6c..45990d4ed 100644 --- a/third_party/less/BUILD.mk +++ b/third_party/less/BUILD.mk @@ -26,6 +26,7 @@ THIRD_PARTY_LESS_DIRECTDEPS = \ LIBC_RUNTIME \ LIBC_STDIO \ LIBC_STR \ + LIBC_SYSTEM \ LIBC_SYSV \ THIRD_PARTY_MUSL \ THIRD_PARTY_NCURSES \ diff --git a/third_party/lua/BUILD.mk b/third_party/lua/BUILD.mk index 805eacb3f..1adb27977 100644 --- a/third_party/lua/BUILD.mk +++ b/third_party/lua/BUILD.mk @@ -131,6 +131,7 @@ THIRD_PARTY_LUA_A_DIRECTDEPS = \ LIBC_RUNTIME \ LIBC_STDIO \ LIBC_STR \ + LIBC_SYSTEM \ LIBC_SYSV \ LIBC_THREAD \ LIBC_TINYMATH \ diff --git a/third_party/lua/llock.c b/third_party/lua/llock.c index 9a0f0bfbb..359140f55 100644 --- a/third_party/lua/llock.c +++ b/third_party/lua/llock.c @@ -19,12 +19,16 @@ #include "libc/thread/thread.h" #include "third_party/lua/lrepl.h" -static pthread_mutex_t lua_repl_lock_obj; +static pthread_mutex_t lua_repl_lock_obj = PTHREAD_MUTEX_INITIALIZER; -void(lua_repl_lock)(void) { +void lua_repl_wock(void) { + lua_repl_lock_obj = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; +} + +void lua_repl_lock(void) { pthread_mutex_lock(&lua_repl_lock_obj); } -void(lua_repl_unlock)(void) { +void lua_repl_unlock(void) { pthread_mutex_unlock(&lua_repl_lock_obj); } diff --git a/third_party/lua/lrepl.h b/third_party/lua/lrepl.h index a2294c5ca..7d08b0730 100644 --- a/third_party/lua/lrepl.h +++ b/third_party/lua/lrepl.h @@ -11,6 +11,7 @@ extern struct linenoiseState *lua_repl_linenoise; extern linenoiseCompletionCallback *lua_repl_completions_callback; void lua_freerepl(void); +void lua_repl_wock(void); void lua_repl_lock(void); void lua_repl_unlock(void); int lua_loadline(lua_State *); diff --git a/third_party/lua/ltests.c b/third_party/lua/ltests.c index 8a9a66347..851284956 100644 --- a/third_party/lua/ltests.c +++ b/third_party/lua/ltests.c @@ -28,25 +28,39 @@ #define ltests_c #define LUA_CORE -#include "third_party/lua/lapi.h" -#include "third_party/lua/lauxlib.h" -#include "third_party/lua/lcode.h" -#include "third_party/lua/lctype.h" -#include "third_party/lua/ldebug.h" -#include "third_party/lua/ldo.h" -#include "third_party/lua/lfunc.h" -#include "third_party/lua/lmem.h" -#include "third_party/lua/lopcodes.h" -#include "third_party/lua/lopnames.inc" -#include "third_party/lua/lprefix.h" -#include "third_party/lua/lstate.h" -#include "third_party/lua/lstring.h" -#include "third_party/lua/ltable.h" -#include "third_party/lua/lua.h" -#include "third_party/lua/lualib.h" +#include "lprefix.h" + +#include +#include +#include +#include +#include +#include "libc/mem/gc.h" +#include "libc/log/log.h" + +#include "lua.h" + +#include "lapi.h" +#include "lauxlib.h" +#include "lcode.h" +#include "lctype.h" +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lmem.h" +#include "lopcodes.h" +#include "lopnames.inc" +#include "lprefix.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "lualib.h" +#include "ltm.h" + __static_yoink("lua_notice"); + /* ** The whole module only makes sense with LUA_DEBUG on */ diff --git a/third_party/lua/lua.h b/third_party/lua/lua.h index f17da40f3..470e0f423 100644 --- a/third_party/lua/lua.h +++ b/third_party/lua/lua.h @@ -133,6 +133,14 @@ typedef struct lua_Debug lua_Debug; typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar); +/* +** [jart] support ltests.h without unsafe LUA_USER_H kludge +** use `make MODE=dbg` to get this functionality +*/ +#ifdef MODE_DBG +#include "ltests.h" +#endif + /* ** generic extra include file */ diff --git a/third_party/lua/lunix.c b/third_party/lua/lunix.c index 06cd1541b..f5007e414 100644 --- a/third_party/lua/lunix.c +++ b/third_party/lua/lunix.c @@ -109,8 +109,9 @@ #include "third_party/lua/lgc.h" #include "third_party/lua/lua.h" #include "third_party/lua/luaconf.h" -#include "third_party/nsync/futex.internal.h" #include "libc/sysv/consts/clock.h" +#include "libc/cosmo.h" +#include "libc/cosmo.h" #include "tool/net/luacheck.h" #define DNS_NAME_MAX 253 @@ -2855,7 +2856,7 @@ static int LuaUnixMemoryWait(lua_State *L) { deadline = &ts; } BEGIN_CANCELATION_POINT; - rc = nsync_futex_wait_((atomic_int *)GetWord(L), expect, + rc = cosmo_futex_wait((atomic_int *)GetWord(L), expect, PTHREAD_PROCESS_SHARED, CLOCK_REALTIME, deadline); END_CANCELATION_POINT; if (rc < 0) errno = -rc, rc = -1; @@ -2867,7 +2868,7 @@ static int LuaUnixMemoryWait(lua_State *L) { static int LuaUnixMemoryWake(lua_State *L) { int count, woken; count = luaL_optinteger(L, 3, INT_MAX); - woken = nsync_futex_wake_((atomic_int *)GetWord(L), count, + woken = cosmo_futex_wake((atomic_int *)GetWord(L), count, PTHREAD_PROCESS_SHARED); npassert(woken >= 0); return ReturnInteger(L, woken); @@ -2958,7 +2959,7 @@ static int LuaUnixMapshared(lua_State *L) { m->mapsize = c; m->lock = (pthread_mutex_t *)p; pthread_mutexattr_init(&mattr); - pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_NORMAL); + pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_DEFAULT); pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED); pthread_mutex_init(m->lock, &mattr); pthread_mutexattr_destroy(&mattr); @@ -3439,12 +3440,14 @@ int LuaUnix(lua_State *L) { LuaSetIntField(L, "SHUT_RDWR", SHUT_RDWR); // recvfrom() / sendto() flags - LuaSetIntField(L, "MSG_WAITALL", MSG_WAITALL); - LuaSetIntField(L, "MSG_DONTROUTE", MSG_DONTROUTE); - LuaSetIntField(L, "MSG_PEEK", MSG_PEEK); LuaSetIntField(L, "MSG_OOB", MSG_OOB); + LuaSetIntField(L, "MSG_PEEK", MSG_PEEK); + LuaSetIntField(L, "MSG_DONTROUTE", MSG_DONTROUTE); + LuaSetIntField(L, "MSG_DONTWAIT", MSG_DONTWAIT); LuaSetIntField(L, "MSG_NOSIGNAL", MSG_NOSIGNAL); - LuaSetIntField(L, "MSG_MORE", MSG_MORE); + LuaSetIntField(L, "MSG_WAITALL", MSG_WAITALL); + LuaSetIntField(L, "MSG_TRUNC", MSG_TRUNC); + LuaSetIntField(L, "MSG_CTRUNC", MSG_CTRUNC); // readdir() type LuaSetIntField(L, "DT_UNKNOWN", DT_UNKNOWN); diff --git a/third_party/musl/glob.h b/third_party/musl/glob.h index d18d25cdd..3aae69da0 100644 --- a/third_party/musl/glob.h +++ b/third_party/musl/glob.h @@ -30,5 +30,11 @@ typedef struct { int glob(const char *, int, int (*)(const char *, int), glob_t *); void globfree(glob_t *); +#ifdef _LARGEFILE64_SOURCE +#define glob64 glob +#define globfree64 globfree +#define glob64_t glob_t +#endif + COSMOPOLITAN_C_END_ #endif /* COSMOPOLITAN_THIRD_PARTY_MUSL_GLOB_H_ */ diff --git a/third_party/nsync/BUILD.mk b/third_party/nsync/BUILD.mk index b2e545c38..0b8ed2923 100644 --- a/third_party/nsync/BUILD.mk +++ b/third_party/nsync/BUILD.mk @@ -27,7 +27,6 @@ THIRD_PARTY_NSYNC_A_DIRECTDEPS = \ LIBC_INTRIN \ LIBC_NEXGEN32E \ LIBC_NT_KERNEL32 \ - LIBC_NT_SYNCHRONIZATION \ LIBC_STR \ LIBC_SYSV \ LIBC_SYSV_CALLS diff --git a/third_party/nsync/README.cosmo b/third_party/nsync/README.cosmo index bf0f49919..fefc56bf6 100644 --- a/third_party/nsync/README.cosmo +++ b/third_party/nsync/README.cosmo @@ -29,9 +29,9 @@ LOCAL CHANGES - Ensure resources such as POSIX semaphores are are released on fork. - - Modified *NSYNC to allocate waiter objects on the stack. We need it - because we use *NSYNC mutexes to implement POSIX mutexes, which are - too low-level to safely depend on malloc, or even mmap in our case. + - Make contended mutexes go 30% faster by using C11 atomics API. This + lets us use weak cas when appropriate. It also avoids a superfluous + relaxed load on failure. This mostly impacts aarch64, not x86_64. - Rewrote most of the semaphore and futex system call support code so it works well with Cosmopolitan's fat runtime portability. *NSYNC's diff --git a/third_party/nsync/atomic.internal.h b/third_party/nsync/atomic.internal.h index 64c3c9412..1b9879f64 100644 --- a/third_party/nsync/atomic.internal.h +++ b/third_party/nsync/atomic.internal.h @@ -85,13 +85,6 @@ static inline int atm_cas_relacq_u32_(nsync_atomic_uint32_ *p, uint32_t o, memory_order_relaxed); } -static inline int atm_cas_seqcst_u32_(nsync_atomic_uint32_ *p, uint32_t o, - uint32_t n) { - return atomic_compare_exchange_strong_explicit(NSYNC_ATOMIC_UINT32_PTR_(p), - &o, n, memory_order_seq_cst, - memory_order_relaxed); -} - #define ATM_CAS_HELPER_(barrier, p, o, n) \ (atm_cas_##barrier##_u32_((p), (o), (n))) @@ -99,7 +92,6 @@ static inline int atm_cas_seqcst_u32_(nsync_atomic_uint32_ *p, uint32_t o, #define ATM_CAS_ACQ(p, o, n) ATM_CAS_HELPER_(acq, (p), (o), (n)) #define ATM_CAS_REL(p, o, n) ATM_CAS_HELPER_(rel, (p), (o), (n)) #define ATM_CAS_RELACQ(p, o, n) ATM_CAS_HELPER_(relacq, (p), (o), (n)) -#define ATM_CAS_SEQCST(p, o, n) ATM_CAS_HELPER_(seqcst, (p), (o), (n)) /* Need a cast to remove "const" from some uses. */ #define ATM_LOAD(p) \ diff --git a/third_party/nsync/common.c b/third_party/nsync/common.c index 6daf2b8c1..3a247169c 100644 --- a/third_party/nsync/common.c +++ b/third_party/nsync/common.c @@ -17,21 +17,18 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/atomic.h" #include "libc/calls/calls.h" -#include "libc/calls/syscall-sysv.internal.h" +#include "libc/calls/calls.h" #include "libc/dce.h" -#include "libc/intrin/directmap.h" +#include "libc/fmt/itoa.h" #include "libc/intrin/dll.h" -#include "libc/intrin/extend.h" -#include "libc/nt/enum/filemapflags.h" -#include "libc/nt/enum/pageflags.h" -#include "libc/nt/memory.h" -#include "libc/nt/runtime.h" -#include "libc/runtime/memtrack.internal.h" +#include "libc/intrin/kprintf.h" +#include "libc/intrin/weaken.h" #include "libc/runtime/runtime.h" #include "libc/stdalign.h" -#include "libc/stdalign.h" +#include "libc/str/str.h" #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/prot.h" +#include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" #include "libc/thread/tls.h" #include "third_party/nsync/atomic.h" @@ -39,6 +36,7 @@ #include "third_party/nsync/common.internal.h" #include "third_party/nsync/mu_semaphore.h" #include "third_party/nsync/mu_semaphore.internal.h" +#include "libc/intrin/cxaatexit.h" #include "third_party/nsync/wait_s.internal.h" __static_yoink("nsync_notice"); @@ -112,6 +110,8 @@ uint32_t nsync_spin_test_and_set_ (nsync_atomic_uint32_ *w, uint32_t test, /* ====================================================================================== */ +#if NSYNC_DEBUG + struct nsync_waiter_s *nsync_dll_nsync_waiter_ (struct Dll *e) { struct nsync_waiter_s *nw = DLL_CONTAINER(struct nsync_waiter_s, q, e); ASSERT (nw->tag == NSYNC_WAITER_TAG); @@ -135,8 +135,13 @@ waiter *nsync_dll_waiter_samecond_ (struct Dll *e) { return (w); } +#endif /* NSYNC_DEBUG */ + /* -------------------------------- */ +// TODO(jart): enforce in dbg mode once off-by-one flake is fixed +#define DETECT_WAITER_LEAKS 0 + #define MASQUE 0x00fffffffffffff8 #define PTR(x) ((uintptr_t)(x) & MASQUE) #define TAG(x) ROL((uintptr_t)(x) & ~MASQUE, 8) @@ -145,6 +150,54 @@ waiter *nsync_dll_waiter_samecond_ (struct Dll *e) { #define ROR(x, n) (((x) >> (n)) | ((x) << (64 - (n)))) static atomic_uintptr_t free_waiters; +static _Atomic(waiter *) all_waiters; + +#if DETECT_WAITER_LEAKS +static atomic_int all_waiters_count; +static atomic_int free_waiters_count; +#endif + +static waiter *get_waiter_for_thread (void) { + return __get_tls()->tib_nsync; +} + +static bool set_waiter_for_thread (waiter *w) { + __get_tls()->tib_nsync = w; + return (true); +} + +#if DETECT_WAITER_LEAKS +__attribute__((__destructor__)) static void reconcile_waiters (void) { + // we can't perform this check if using exit() with threads + if (!pthread_orphan_np ()) + return; + waiter *w; + if ((w = get_waiter_for_thread ())) { + nsync_waiter_destroy_ (w); + set_waiter_for_thread (0); + } + if (all_waiters_count != free_waiters_count) { + char ibuf[2][12]; + FormatInt32 (ibuf[0], all_waiters_count); + FormatInt32 (ibuf[1], free_waiters_count); + tinyprint (2, "error: nsync panic: all_waiter_count (", + ibuf[0], ") != free_waiters_count (", ibuf[1], + ")\n", NULL); + _Exit (156); + } +} +#endif + +static void all_waiters_push (waiter *w) { + w->next_all = atomic_load_explicit (&all_waiters, memory_order_relaxed); + while (!atomic_compare_exchange_weak_explicit (&all_waiters, &w->next_all, w, + memory_order_acq_rel, + memory_order_relaxed)) + pthread_pause_np (); +#if DETECT_WAITER_LEAKS + ++all_waiters_count; +#endif +} static void free_waiters_push (waiter *w) { uintptr_t tip; @@ -152,14 +205,16 @@ static void free_waiters_push (waiter *w) { tip = atomic_load_explicit (&free_waiters, memory_order_relaxed); for (;;) { w->next_free = (waiter *) PTR (tip); - if (atomic_compare_exchange_weak_explicit (&free_waiters, - &tip, + if (atomic_compare_exchange_weak_explicit (&free_waiters, &tip, ABA (w, TAG (tip) + 1), memory_order_release, memory_order_relaxed)) break; pthread_pause_np (); } +#if DETECT_WAITER_LEAKS + ++free_waiters_count; +#endif } static waiter *free_waiters_pop (void) { @@ -167,39 +222,47 @@ static waiter *free_waiters_pop (void) { uintptr_t tip; tip = atomic_load_explicit (&free_waiters, memory_order_relaxed); while ((w = (waiter *) PTR (tip))) { - if (atomic_compare_exchange_weak_explicit (&free_waiters, - &tip, + if (atomic_compare_exchange_weak_explicit (&free_waiters, &tip, ABA (w->next_free, TAG (tip) + 1), memory_order_acquire, memory_order_relaxed)) break; pthread_pause_np (); } - return w; +#if DETECT_WAITER_LEAKS + if (w) + --free_waiters_count; +#endif + return (w); } -static void free_waiters_populate (void) { +static bool free_waiters_populate (void) { int n; if (IsNetbsd ()) { - // netbsd needs a real file descriptor per semaphore - // tim cook wants us to use his lol central dispatch + // netbsd semaphores are file descriptors n = 1; } else { - n = __pagesize / sizeof(waiter); + // don't create too much fork() overhead + n = 16; } waiter *waiters = mmap (0, n * sizeof(waiter), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (waiters == MAP_FAILED) - nsync_panic_ ("out of memory\n"); + return (false); for (size_t i = 0; i < n; ++i) { waiter *w = &waiters[i]; +#if NSYNC_DEBUG w->tag = WAITER_TAG; w->nw.tag = NSYNC_WAITER_TAG; +#endif if (!nsync_mu_semaphore_init (&w->sem)) { - if (!i) - nsync_panic_ ("out of semaphores\n"); + if (!i) { + // netbsd can run out of semaphores + munmap (waiters, n * sizeof (waiter)); + return (false); + } break; } w->nw.sem = &w->sem; @@ -207,39 +270,31 @@ static void free_waiters_populate (void) { w->nw.flags = NSYNC_WAITER_FLAG_MUCV; dll_init (&w->same_condition); free_waiters_push (w); + all_waiters_push (w); } + return (true); } /* -------------------------------- */ -#define waiter_for_thread __get_tls()->tib_nsync - -void nsync_waiter_destroy (void *v) { - waiter *w = (waiter *) v; - /* Reset waiter_for_thread in case another thread-local variable reuses - the waiter in its destructor while the waiter is taken by the other - thread from free_waiters. This can happen as the destruction order - of thread-local variables can be arbitrary in some platform e.g. - POSIX. */ - waiter_for_thread = NULL; - ASSERT ((w->flags & (WAITER_RESERVED|WAITER_IN_USE)) == WAITER_RESERVED); - w->flags &= ~WAITER_RESERVED; - free_waiters_push (w); -} - /* Return a pointer to an unused waiter struct. Ensures that the enclosed timer is stopped and its channel drained. */ waiter *nsync_waiter_new_ (void) { waiter *w; waiter *tw; - tw = waiter_for_thread; - w = tw; + bool out_of_semaphores = false; + w = tw = get_waiter_for_thread (); if (w == NULL || (w->flags & (WAITER_RESERVED|WAITER_IN_USE)) != WAITER_RESERVED) { - while (!(w = free_waiters_pop ())) - free_waiters_populate (); + while (!(w = free_waiters_pop ())) { + if (!out_of_semaphores) + if (!free_waiters_populate ()) + out_of_semaphores = true; + if (out_of_semaphores) + pthread_yield_np (); + } if (tw == NULL) { - w->flags |= WAITER_RESERVED; - waiter_for_thread = w; + if (set_waiter_for_thread (w)) + w->flags |= WAITER_RESERVED; } } w->flags |= WAITER_IN_USE; @@ -249,14 +304,75 @@ waiter *nsync_waiter_new_ (void) { /* Return an unused waiter struct *w to the free pool. */ void nsync_waiter_free_ (waiter *w) { ASSERT ((w->flags & WAITER_IN_USE) != 0); + w->wipe_mu = NULL; + w->wipe_cv = NULL; w->flags &= ~WAITER_IN_USE; if ((w->flags & WAITER_RESERVED) == 0) { + if (w == get_waiter_for_thread ()) + set_waiter_for_thread (0); free_waiters_push (w); - if (w == waiter_for_thread) - waiter_for_thread = 0; } } +/* Destroys waiter associated with dead thread. */ +void nsync_waiter_destroy_ (void *v) { + waiter *w = (waiter *) v; + ASSERT ((w->flags & (WAITER_RESERVED|WAITER_IN_USE)) == WAITER_RESERVED); + w->flags &= ~WAITER_RESERVED; + free_waiters_push (w); +} + +/* Ravages nsync waiters/locks/conds after fork(). */ +void nsync_waiter_wipe_ (void) { + int n = 0; + waiter *w; + waiter *next; + waiter *prev = 0; + waiter *wall = atomic_load_explicit (&all_waiters, memory_order_relaxed); + for (w = wall; w; w = w->next_all) + nsync_mu_semaphore_destroy (&w->sem); + for (w = wall; w; w = next) { + next = w->next_all; + w->flags = 0; +#if NSYNC_DEBUG + w->tag = WAITER_TAG; + w->nw.tag = NSYNC_WAITER_TAG; +#endif + w->nw.flags = NSYNC_WAITER_FLAG_MUCV; + atomic_init(&w->nw.waiting, 0); + w->l_type = 0; + w->cond.f = 0; + w->cond.v = 0; + w->cond.eq = 0; + dll_init (&w->same_condition); + if (w->wipe_mu) { + atomic_init(&w->wipe_mu->word, 0); + w->wipe_mu->waiters = 0; + } + if (w->wipe_cv) { + atomic_init(&w->wipe_cv->word, 0); + w->wipe_cv->waiters = 0; + } + if (!nsync_mu_semaphore_init (&w->sem)) + continue; /* leak it */ + w->next_free = prev; + w->next_all = prev; + prev = w; + ++n; + } +#if DETECT_WAITER_LEAKS + atomic_init (&all_waiters_count, n); + atomic_init (&free_waiters_count, n); +#else + (void)n; +#endif + atomic_init (&free_waiters, prev); + atomic_init (&all_waiters, prev); + for (struct Dll *e = dll_first (_pthread_list); e; + e = dll_next (_pthread_list, e)) + POSIXTHREAD_CONTAINER (e)->tib->tib_nsync = 0; +} + /* ====================================================================================== */ /* writer_type points to a lock_type that describes how to manipulate a mu for a writer. */ diff --git a/third_party/nsync/common.internal.h b/third_party/nsync/common.internal.h index 43b8b3c48..e24d1071a 100644 --- a/third_party/nsync/common.internal.h +++ b/third_party/nsync/common.internal.h @@ -9,15 +9,10 @@ #include "third_party/nsync/mu_semaphore.h" #include "third_party/nsync/note.h" #include "third_party/nsync/time.h" +#include "third_party/nsync/defs.h" #include "third_party/nsync/wait_s.internal.h" COSMOPOLITAN_C_START_ -#ifdef MODE_DBG -#define NSYNC_DEBUG 1 -#else -#define NSYNC_DEBUG 0 -#endif - /* Yield the CPU. Platform specific. */ void nsync_yield_(void); @@ -154,7 +149,7 @@ extern lock_type *nsync_reader_type_; /* ---------- */ -/* Hold a pair of condition function and its argument. */ +/* Hold a pair of condition function and its argument. */ struct wait_condition_s { int (*f)(const void *v); const void *v; @@ -191,18 +186,21 @@ struct wait_condition_s { ATM_STORE_REL (&w.waiting, 0); nsync_mu_semaphore_v (&w.sem); */ typedef struct waiter_s { - uint32_t tag; /* debug DLL_NSYNC_WAITER, DLL_WAITER, DLL_WAITER_SAMECOND */ - int flags; /* see WAITER_* bits below */ - nsync_semaphore sem; /* Thread waits on this semaphore. */ - struct nsync_waiter_s nw; /* An embedded nsync_waiter_s. */ - struct nsync_mu_s_ *cv_mu; /* pointer to nsync_mu associated with a cv wait */ - lock_type - *l_type; /* Lock type of the mu, or nil if not associated with a mu. */ - nsync_atomic_uint32_ remove_count; /* count of removals from queue */ +#if NSYNC_DEBUG + uint32_t tag; /* Debug DLL_NSYNC_WAITER, DLL_WAITER, DLL_WAITER_SAMECOND. */ +#endif + int flags; /* See WAITER_* bits below. */ + nsync_atomic_uint32_ remove_count; /* Monotonic count of removals from queue. */ + nsync_semaphore sem; /* Thread waits on this semaphore. */ + struct nsync_waiter_s nw; /* An embedded nsync_waiter_s. */ + struct nsync_mu_s_ *cv_mu; /* Pointer to nsync_mu associated with a cv wait. */ + lock_type *l_type; /* Lock type of the mu, or nil if not associated with a mu. */ struct wait_condition_s cond; /* A condition on which to acquire a mu. */ - struct Dll same_condition; /* Links neighbours in nw.q with same - non-nil condition. */ + struct Dll same_condition; /* Links neighbours in nw.q with same non-nil condition. */ + struct waiter_s * next_all; struct waiter_s * next_free; + struct nsync_mu_s_ *wipe_mu; + struct nsync_cv_s_ *wipe_cv; } waiter; static const uint32_t WAITER_TAG = 0x0590239f; static const uint32_t NSYNC_WAITER_TAG = 0x726d2ba9; diff --git a/third_party/nsync/defs.h b/third_party/nsync/defs.h new file mode 100644 index 000000000..73b5c0752 --- /dev/null +++ b/third_party/nsync/defs.h @@ -0,0 +1,12 @@ +#ifndef COSMOPOLITAN_THIRD_PARTY_NSYNC_DEFS_H_ +#define COSMOPOLITAN_THIRD_PARTY_NSYNC_DEFS_H_ +COSMOPOLITAN_C_START_ + +#ifdef MODE_DBG +#define NSYNC_DEBUG 1 +#else +#define NSYNC_DEBUG 0 +#endif + +COSMOPOLITAN_C_END_ +#endif /* COSMOPOLITAN_THIRD_PARTY_NSYNC_DEFS_H_ */ diff --git a/third_party/nsync/futex.internal.h b/third_party/nsync/futex.internal.h deleted file mode 100644 index f555224ff..000000000 --- a/third_party/nsync/futex.internal.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef NSYNC_FUTEX_INTERNAL_H_ -#define NSYNC_FUTEX_INTERNAL_H_ -#include "libc/calls/struct/timespec.h" -#include "libc/dce.h" -COSMOPOLITAN_C_START_ - -#ifndef __cplusplus -#define _FUTEX_ATOMIC(x) _Atomic(x) -#else -#define _FUTEX_ATOMIC(x) x -#endif - -int nsync_futex_wake_(_FUTEX_ATOMIC(int) *, int, char); -int nsync_futex_wait_(_FUTEX_ATOMIC(int) *, int, char, int, const struct timespec *); - -COSMOPOLITAN_C_END_ -#endif /* NSYNC_FUTEX_INTERNAL_H_ */ diff --git a/third_party/nsync/mem/nsync_cv.c b/third_party/nsync/mem/nsync_cv.c index 9e798d4eb..c871c581d 100644 --- a/third_party/nsync/mem/nsync_cv.c +++ b/third_party/nsync/mem/nsync_cv.c @@ -286,6 +286,8 @@ int nsync_cv_wait_with_deadline_generic (nsync_cv *pcv, void *pmu, IGNORE_RACES_START (); c.w = nsync_waiter_new_ (); + c.w->wipe_cv = pcv; + c.w->wipe_mu = pmu; c.clock = clock; c.abs_deadline = abs_deadline; c.cancel_note = cancel_note; diff --git a/third_party/nsync/mem/nsync_debug.c b/third_party/nsync/mem/nsync_debug.c index a3d847286..8c7d7e124 100644 --- a/third_party/nsync/mem/nsync_debug.c +++ b/third_party/nsync/mem/nsync_debug.c @@ -20,6 +20,7 @@ #include "third_party/nsync/common.internal.h" #include "third_party/nsync/mu_semaphore.h" #include "third_party/nsync/races.internal.h" +#include "third_party/nsync/defs.h" #include "third_party/nsync/wait_s.internal.h" __static_yoink("nsync_notice"); @@ -148,15 +149,23 @@ static void emit_waiters (struct emit_buf *b, struct Dll *list) { waiter *w = DLL_WAITER (p); next = NULL; emit_print (b, " %i", (uintptr_t) w); +#if NSYNC_DEBUG if (w->tag != WAITER_TAG) { emit_print (b, "bad WAITER_TAG %i", (uintptr_t) w->tag); } else { +#else + { +#endif next = dll_next (list, p); +#if NSYNC_DEBUG if (nw->tag != NSYNC_WAITER_TAG) { emit_print (b, " bad WAITER_TAG %i", (uintptr_t) nw->tag); } else { +#else + { +#endif emit_print (b, " embedded=%i waiting=%i", (uintptr_t) (w->flags & NSYNC_WAITER_FLAG_MUCV), (uintptr_t) ATM_LOAD (&nw->waiting)); diff --git a/third_party/nsync/mem/nsync_sem_wait.c b/third_party/nsync/mem/nsync_sem_wait.c index 9ee5044c5..059fd456a 100644 --- a/third_party/nsync/mem/nsync_sem_wait.c +++ b/third_party/nsync/mem/nsync_sem_wait.c @@ -40,7 +40,9 @@ int nsync_sem_wait_with_cancel_ (waiter *w, int clock, nsync_time abs_deadline, sem_outcome = ECANCELED; if (nsync_time_cmp (cancel_time, nsync_time_zero) > 0) { struct nsync_waiter_s nw; +#if NSYNC_DEBUG nw.tag = NSYNC_WAITER_TAG; +#endif nw.sem = &w->sem; dll_init (&nw.q); ATM_STORE (&nw.waiting, 1); diff --git a/third_party/nsync/mem/nsync_wait.c b/third_party/nsync/mem/nsync_wait.c index 6cbebd3e1..1bf5bdeb2 100644 --- a/third_party/nsync/mem/nsync_wait.c +++ b/third_party/nsync/mem/nsync_wait.c @@ -51,7 +51,9 @@ int nsync_wait_n (void *mu, void (*lock) (void *), void (*unlock) (void *), nw = (struct nsync_waiter_s *) malloc (count * sizeof (nw[0])); } for (i = 0; i != count && enqueued; i++) { +#if NSYNC_DEBUG nw[i].tag = NSYNC_WAITER_TAG; +#endif nw[i].sem = &w->sem; dll_init (&nw[i].q); ATM_STORE (&nw[i].waiting, 0); diff --git a/third_party/nsync/mu.c b/third_party/nsync/mu.c index ed6a287ff..6da4d14a8 100644 --- a/third_party/nsync/mu.c +++ b/third_party/nsync/mu.c @@ -34,9 +34,11 @@ void nsync_mu_init (nsync_mu *mu) { /* Release the mutex spinlock. */ static void mu_release_spinlock (nsync_mu *mu) { - uint32_t old_word = ATM_LOAD (&mu->word); - while (!ATM_CAS_REL (&mu->word, old_word, old_word & ~MU_SPINLOCK)) { - old_word = ATM_LOAD (&mu->word); + uint32_t old_word = atomic_load_explicit (&mu->word, + memory_order_relaxed); + while (!atomic_compare_exchange_weak_explicit ( + &mu->word, &old_word, old_word & ~MU_SPINLOCK, + memory_order_release, memory_order_relaxed)) { } } @@ -55,6 +57,7 @@ void nsync_mu_lock_slow_ (nsync_mu *mu, waiter *w, uint32_t clear, lock_type *l_ w->cond.f = NULL; /* Not using a conditional critical section. */ w->cond.v = NULL; w->cond.eq = NULL; + w->wipe_mu = mu; w->l_type = l_type; zero_to_acquire = l_type->zero_to_acquire; if (clear != 0) { @@ -68,15 +71,17 @@ void nsync_mu_lock_slow_ (nsync_mu *mu, waiter *w, uint32_t clear, lock_type *l_ if ((old_word & zero_to_acquire) == 0) { /* lock can be acquired; try to acquire, possibly clearing MU_DESIG_WAKER and MU_LONG_WAIT. */ - if (ATM_CAS_ACQ (&mu->word, old_word, - (old_word+l_type->add_to_acquire) & - ~(clear|long_wait|l_type->clear_on_acquire))) { + if (atomic_compare_exchange_weak_explicit (&mu->word, &old_word, + (old_word+l_type->add_to_acquire) & + ~(clear|long_wait|l_type->clear_on_acquire), + memory_order_acquire, memory_order_relaxed)) { break; } } else if ((old_word&MU_SPINLOCK) == 0 && - ATM_CAS_ACQ (&mu->word, old_word, - (old_word|MU_SPINLOCK|long_wait| - l_type->set_when_waiting) & ~(clear | MU_ALL_FALSE))) { + atomic_compare_exchange_weak_explicit (&mu->word, &old_word, + (old_word|MU_SPINLOCK|long_wait| + l_type->set_when_waiting) & ~(clear | MU_ALL_FALSE), + memory_order_acquire, memory_order_relaxed)) { /* Spinlock is now held, and lock is held by someone else; MU_WAITING has also been set; queue ourselves. @@ -133,13 +138,16 @@ void nsync_mu_lock_slow_ (nsync_mu *mu, waiter *w, uint32_t clear, lock_type *l_ int nsync_mu_trylock (nsync_mu *mu) { int result; IGNORE_RACES_START (); - if (ATM_CAS_ACQ (&mu->word, 0, MU_WADD_TO_ACQUIRE)) { /* acquire CAS */ + uint32_t old_word = 0; + if (atomic_compare_exchange_strong_explicit (&mu->word, &old_word, MU_WADD_TO_ACQUIRE, + memory_order_acquire, memory_order_relaxed)) { result = 1; } else { - uint32_t old_word = ATM_LOAD (&mu->word); result = ((old_word & MU_WZERO_TO_ACQUIRE) == 0 && - ATM_CAS_ACQ (&mu->word, old_word, - (old_word + MU_WADD_TO_ACQUIRE) & ~MU_WCLEAR_ON_ACQUIRE)); + atomic_compare_exchange_strong_explicit ( + &mu->word, &old_word, + (old_word + MU_WADD_TO_ACQUIRE) & ~MU_WCLEAR_ON_ACQUIRE, + memory_order_acquire, memory_order_relaxed)); } IGNORE_RACES_END (); return (result); @@ -148,11 +156,13 @@ int nsync_mu_trylock (nsync_mu *mu) { /* Block until *mu is free and then acquire it in writer mode. */ void nsync_mu_lock (nsync_mu *mu) { IGNORE_RACES_START (); - if (!ATM_CAS_ACQ (&mu->word, 0, MU_WADD_TO_ACQUIRE)) { /* acquire CAS */ - uint32_t old_word = ATM_LOAD (&mu->word); + uint32_t old_word = 0; + if (!atomic_compare_exchange_strong_explicit (&mu->word, &old_word, MU_WADD_TO_ACQUIRE, + memory_order_acquire, memory_order_relaxed)) { if ((old_word&MU_WZERO_TO_ACQUIRE) != 0 || - !ATM_CAS_ACQ (&mu->word, old_word, - (old_word+MU_WADD_TO_ACQUIRE) & ~MU_WCLEAR_ON_ACQUIRE)) { + !atomic_compare_exchange_strong_explicit (&mu->word, &old_word, + (old_word+MU_WADD_TO_ACQUIRE) & ~MU_WCLEAR_ON_ACQUIRE, + memory_order_acquire, memory_order_relaxed)) { LOCKTRACE("acquiring nsync_mu_lock(%t)...", mu); waiter *w = nsync_waiter_new_ (); nsync_mu_lock_slow_ (mu, w, 0, nsync_writer_type_); @@ -169,13 +179,15 @@ void nsync_mu_lock (nsync_mu *mu) { int nsync_mu_rtrylock (nsync_mu *mu) { int result; IGNORE_RACES_START (); - if (ATM_CAS_ACQ (&mu->word, 0, MU_RADD_TO_ACQUIRE)) { /* acquire CAS */ + uint32_t old_word = 0; + if (atomic_compare_exchange_strong_explicit (&mu->word, &old_word, MU_RADD_TO_ACQUIRE, + memory_order_acquire, memory_order_relaxed)) { result = 1; } else { - uint32_t old_word = ATM_LOAD (&mu->word); result = ((old_word&MU_RZERO_TO_ACQUIRE) == 0 && - ATM_CAS_ACQ (&mu->word, old_word, - (old_word+MU_RADD_TO_ACQUIRE) & ~MU_RCLEAR_ON_ACQUIRE)); + atomic_compare_exchange_strong_explicit (&mu->word, &old_word, + (old_word+MU_RADD_TO_ACQUIRE) & ~MU_RCLEAR_ON_ACQUIRE, + memory_order_acquire, memory_order_relaxed)); } IGNORE_RACES_END (); return (result); @@ -184,11 +196,14 @@ int nsync_mu_rtrylock (nsync_mu *mu) { /* Block until *mu can be acquired in reader mode and then acquire it. */ void nsync_mu_rlock (nsync_mu *mu) { IGNORE_RACES_START (); - if (!ATM_CAS_ACQ (&mu->word, 0, MU_RADD_TO_ACQUIRE)) { /* acquire CAS */ - uint32_t old_word = ATM_LOAD (&mu->word); + uint32_t old_word = 0; + if (!atomic_compare_exchange_strong_explicit (&mu->word, &old_word, MU_RADD_TO_ACQUIRE, + memory_order_acquire, memory_order_relaxed)) { if ((old_word&MU_RZERO_TO_ACQUIRE) != 0 || - !ATM_CAS_ACQ (&mu->word, old_word, - (old_word+MU_RADD_TO_ACQUIRE) & ~MU_RCLEAR_ON_ACQUIRE)) { + !atomic_compare_exchange_strong_explicit (&mu->word, &old_word, + (old_word+MU_RADD_TO_ACQUIRE) & ~MU_RCLEAR_ON_ACQUIRE, + memory_order_acquire, memory_order_relaxed)) { + LOCKTRACE("acquiring nsync_mu_rlock(%t)...", mu); waiter *w = nsync_waiter_new_ (); nsync_mu_lock_slow_ (mu, w, 0, nsync_reader_type_); nsync_waiter_free_ (w); @@ -236,16 +251,16 @@ struct Dll *nsync_remove_from_mu_queue_ (struct Dll *mu_queue, struct Dll *e) { /* Record previous and next elements in the original queue. */ struct Dll *prev = e->prev; struct Dll *next = e->next; - uint32_t old_value; /* Remove. */ dll_remove (&mu_queue, e); - do { - old_value = ATM_LOAD (&DLL_WAITER (e)->remove_count); - } while (!ATM_CAS (&DLL_WAITER (e)->remove_count, old_value, old_value+1)); + uint32_t old_value = ATM_LOAD (&DLL_WAITER (e)->remove_count); + while (!atomic_compare_exchange_weak_explicit ( + &DLL_WAITER (e)->remove_count, &old_value, old_value+1, + memory_order_relaxed, memory_order_relaxed)) { + } if (!dll_is_empty (mu_queue)) { /* Fix up same_condition. */ struct Dll *e_same_condition = &DLL_WAITER (e)->same_condition; - if (e_same_condition->next != e_same_condition) { /* *e is linked to a same_condition neighbour---just remove it. */ e_same_condition->next->prev = e_same_condition->prev; @@ -290,14 +305,18 @@ void nsync_mu_unlock_slow_ (nsync_mu *mu, lock_type *l_type) { /* no one to wake, there's a designated waker waking up, there are still readers, or it's a reader and all waiters have false conditions */ - if (ATM_CAS_REL (&mu->word, old_word, - (old_word - l_type->add_to_acquire) & - ~l_type->clear_on_uncontended_release)) { + if (atomic_compare_exchange_weak_explicit ( + &mu->word, &old_word, + (old_word - l_type->add_to_acquire) & + ~l_type->clear_on_uncontended_release, + memory_order_release, memory_order_relaxed)) { return; } } else if ((old_word&MU_SPINLOCK) == 0 && - ATM_CAS_SEQCST (&mu->word, old_word, /* [jart] fixes issues on apple silicon */ - (old_word-early_release_mu)|MU_SPINLOCK|MU_DESIG_WAKER)) { + atomic_compare_exchange_weak_explicit ( + &mu->word, &old_word, + (old_word-early_release_mu)|MU_SPINLOCK|MU_DESIG_WAKER, + memory_order_acq_rel, memory_order_relaxed)) { struct Dll *wake; lock_type *wake_type; uint32_t clear_on_release; @@ -433,10 +452,10 @@ void nsync_mu_unlock_slow_ (nsync_mu *mu, lock_type *l_type) { whether any waiters remain, and whether any of them are writers. */ old_word = ATM_LOAD (&mu->word); - while (!ATM_CAS_REL (&mu->word, old_word, - ((old_word-late_release_mu)|set_on_release) & - ~clear_on_release)) { /* release CAS */ - old_word = ATM_LOAD (&mu->word); + while (!atomic_compare_exchange_weak_explicit ( + &mu->word, &old_word, + ((old_word - late_release_mu) | set_on_release) & ~clear_on_release, + memory_order_release, memory_order_relaxed)) { } /* Wake the waiters. */ for (p = dll_first (wake); p != NULL; p = next) { @@ -459,8 +478,10 @@ void nsync_mu_unlock (nsync_mu *mu) { waiter. Another thread could acquire, decrement a reference count and deallocate the mutex before the current thread touched the mutex word again. */ - if (!ATM_CAS_REL (&mu->word, MU_WLOCK, 0)) { - uint32_t old_word = ATM_LOAD (&mu->word); + uint32_t old_word = MU_WLOCK; + if (!atomic_compare_exchange_strong_explicit (&mu->word, &old_word, 0, + memory_order_release, + memory_order_relaxed)) { /* Clear MU_ALL_FALSE because the critical section we're just leaving may have made some conditions true. */ uint32_t new_word = (old_word - MU_WLOCK) & ~MU_ALL_FALSE; @@ -488,8 +509,10 @@ void nsync_mu_unlock (nsync_mu *mu) { void nsync_mu_runlock (nsync_mu *mu) { IGNORE_RACES_START (); /* See comment in nsync_mu_unlock(). */ - if (!ATM_CAS_REL (&mu->word, MU_RLOCK, 0)) { - uint32_t old_word = ATM_LOAD (&mu->word); + uint32_t old_word = MU_RLOCK; + if (!atomic_compare_exchange_strong_explicit (&mu->word, &old_word, 0, + memory_order_release, + memory_order_relaxed)) { /* Sanity check: mutex must not be held in write mode and reader count must not be 0. */ if (((old_word ^ MU_WLOCK) & (MU_WLOCK | MU_RLOCK_FIELD)) == 0) { diff --git a/third_party/nsync/mu.h b/third_party/nsync/mu.h index dab1ed722..4831cacd8 100644 --- a/third_party/nsync/mu.h +++ b/third_party/nsync/mu.h @@ -48,7 +48,6 @@ COSMOPOLITAN_C_START_ */ typedef struct nsync_mu_s_ { nsync_atomic_uint32_ word; /* internal use only */ - int _zero; /* c pthread_mutex_t */ struct Dll *waiters; /* internal use only */ } nsync_mu; diff --git a/third_party/nsync/mu_semaphore.c b/third_party/nsync/mu_semaphore.c index b3eb68255..cc6906400 100644 --- a/third_party/nsync/mu_semaphore.c +++ b/third_party/nsync/mu_semaphore.c @@ -30,6 +30,15 @@ bool nsync_mu_semaphore_init (nsync_semaphore *s) { } } +/* Destroy *s. */ +void nsync_mu_semaphore_destroy (nsync_semaphore *s) { + if (IsNetbsd ()) { + return nsync_mu_semaphore_destroy_sem (s); + } else { + return nsync_mu_semaphore_destroy_futex (s); + } +} + /* Wait until the count of *s exceeds 0, and decrement it. If POSIX cancellations are currently disabled by the thread, then this function always succeeds. When they're enabled in MASKED mode, this function may return ECANCELED. Otherwise, diff --git a/third_party/nsync/mu_semaphore.h b/third_party/nsync/mu_semaphore.h index 634d9fea4..fffb99e51 100644 --- a/third_party/nsync/mu_semaphore.h +++ b/third_party/nsync/mu_semaphore.h @@ -10,6 +10,9 @@ typedef struct nsync_semaphore_s_ { /* Initialize *s; the initial value is 0. */ bool nsync_mu_semaphore_init(nsync_semaphore *s); +/* Destroy *s. */ +void nsync_mu_semaphore_destroy(nsync_semaphore *s); + /* Wait until the count of *s exceeds 0, and decrement it. */ errno_t nsync_mu_semaphore_p(nsync_semaphore *s); diff --git a/third_party/nsync/mu_semaphore.internal.h b/third_party/nsync/mu_semaphore.internal.h index 6d8167d78..6fe15090f 100755 --- a/third_party/nsync/mu_semaphore.internal.h +++ b/third_party/nsync/mu_semaphore.internal.h @@ -5,19 +5,16 @@ COSMOPOLITAN_C_START_ bool nsync_mu_semaphore_init_futex(nsync_semaphore *); +void nsync_mu_semaphore_destroy_futex(nsync_semaphore *); errno_t nsync_mu_semaphore_p_futex(nsync_semaphore *); errno_t nsync_mu_semaphore_p_with_deadline_futex(nsync_semaphore *, int, nsync_time); void nsync_mu_semaphore_v_futex(nsync_semaphore *); bool nsync_mu_semaphore_init_sem(nsync_semaphore *); +void nsync_mu_semaphore_destroy_sem(nsync_semaphore *); errno_t nsync_mu_semaphore_p_sem(nsync_semaphore *); errno_t nsync_mu_semaphore_p_with_deadline_sem(nsync_semaphore *, int, nsync_time); void nsync_mu_semaphore_v_sem(nsync_semaphore *); -bool nsync_mu_semaphore_init_gcd(nsync_semaphore *); -errno_t nsync_mu_semaphore_p_gcd(nsync_semaphore *); -errno_t nsync_mu_semaphore_p_with_deadline_gcd(nsync_semaphore *, int, nsync_time); -void nsync_mu_semaphore_v_gcd(nsync_semaphore *); - COSMOPOLITAN_C_END_ #endif /* COSMOPOLITAN_THIRD_PARTY_NSYNC_MU_SEMAPHORE_INTERNAL_H_ */ diff --git a/third_party/nsync/mu_semaphore_futex.c b/third_party/nsync/mu_semaphore_futex.c index 863773da1..cc556267d 100644 --- a/third_party/nsync/mu_semaphore_futex.c +++ b/third_party/nsync/mu_semaphore_futex.c @@ -21,7 +21,9 @@ #include "libc/thread/thread.h" #include "third_party/nsync/atomic.h" #include "third_party/nsync/atomic.internal.h" -#include "third_party/nsync/futex.internal.h" +#include "libc/cosmo.h" +#include "libc/calls/struct/timespec.h" +#include "libc/cosmo.h" #include "third_party/nsync/mu_semaphore.internal.h" /** @@ -49,6 +51,9 @@ bool nsync_mu_semaphore_init_futex (nsync_semaphore *s) { return true; } +void nsync_mu_semaphore_destroy_futex (nsync_semaphore *s) { +} + /* Wait until the count of *s exceeds 0, and decrement it. If POSIX cancellations are currently disabled by the thread, then this function always succeeds. When they're enabled in MASKED mode, this function may return ECANCELED. Otherwise, @@ -61,7 +66,7 @@ errno_t nsync_mu_semaphore_p_futex (nsync_semaphore *s) { i = ATM_LOAD ((nsync_atomic_uint32_ *) &f->i); if (i == 0) { int futex_result; - futex_result = -nsync_futex_wait_ ( + futex_result = -cosmo_futex_wait ( (atomic_int *)&f->i, i, PTHREAD_PROCESS_PRIVATE, 0, 0); ASSERT (futex_result == 0 || @@ -73,7 +78,10 @@ errno_t nsync_mu_semaphore_p_futex (nsync_semaphore *s) { result = ECANCELED; } } - } while (result == 0 && (i == 0 || !ATM_CAS_ACQ ((nsync_atomic_uint32_ *) &f->i, i, i-1))); + } while (result == 0 && (i == 0 || + !atomic_compare_exchange_weak_explicit ( + (nsync_atomic_uint32_ *) &f->i, &i, i-1, + memory_order_acquire, memory_order_relaxed))); return result; } @@ -97,9 +105,9 @@ errno_t nsync_mu_semaphore_p_with_deadline_futex (nsync_semaphore *s, int clock, ts_buf.tv_nsec = NSYNC_TIME_NSEC (abs_deadline); ts = &ts_buf; } - futex_result = nsync_futex_wait_ ((atomic_int *)&f->i, i, - PTHREAD_PROCESS_PRIVATE, - clock, ts); + futex_result = cosmo_futex_wait ((atomic_int *)&f->i, i, + PTHREAD_PROCESS_PRIVATE, + clock, ts); ASSERT (futex_result == 0 || futex_result == -EINTR || futex_result == -EAGAIN || @@ -118,16 +126,20 @@ errno_t nsync_mu_semaphore_p_with_deadline_futex (nsync_semaphore *s, int clock, result = ECANCELED; } } - } while (result == 0 && (i == 0 || !ATM_CAS_ACQ ((nsync_atomic_uint32_ *) &f->i, i, i - 1))); + } while (result == 0 && (i == 0 || + !atomic_compare_exchange_weak_explicit ( + (nsync_atomic_uint32_ *) &f->i, &i, i-1, + memory_order_acquire, memory_order_relaxed))); return (result); } /* Ensure that the count of *s is at least 1. */ void nsync_mu_semaphore_v_futex (nsync_semaphore *s) { struct futex *f = (struct futex *) s; - uint32_t old_value; - do { - old_value = ATM_LOAD ((nsync_atomic_uint32_ *) &f->i); - } while (!ATM_CAS_REL ((nsync_atomic_uint32_ *) &f->i, old_value, old_value+1)); - ASSERT (nsync_futex_wake_ ((atomic_int *)&f->i, 1, PTHREAD_PROCESS_PRIVATE) >= 0); + uint32_t old_value = ATM_LOAD ((nsync_atomic_uint32_ *) &f->i); + while (!atomic_compare_exchange_weak_explicit ( + (nsync_atomic_uint32_ *) &f->i, &old_value, old_value+1, + memory_order_release, memory_order_relaxed)) { + } + ASSERT (cosmo_futex_wake ((atomic_int *)&f->i, 1, PTHREAD_PROCESS_PRIVATE) >= 0); } diff --git a/third_party/nsync/mu_semaphore_sem.c b/third_party/nsync/mu_semaphore_sem.c index 41d92acfa..a42b2e8c3 100644 --- a/third_party/nsync/mu_semaphore_sem.c +++ b/third_party/nsync/mu_semaphore_sem.c @@ -33,7 +33,6 @@ #include "third_party/nsync/time.h" #include "third_party/nsync/mu_semaphore.h" #include "libc/intrin/atomic.h" -#include "libc/atomic.h" #include "third_party/nsync/time.h" /** @@ -44,23 +43,14 @@ struct sem { int64_t id; - struct sem *next; }; -static _Atomic(struct sem *) g_sems; - static nsync_semaphore *sem_big_enough_for_sem = (nsync_semaphore *) (uintptr_t)(1 / (sizeof (struct sem) <= sizeof (*sem_big_enough_for_sem))); -static void sems_push (struct sem *f) { - f->next = atomic_load_explicit (&g_sems, memory_order_relaxed); - while (!atomic_compare_exchange_weak_explicit (&g_sems, &f->next, f, - memory_order_acq_rel, - memory_order_relaxed)) - pthread_pause_np (); -} - -static bool nsync_mu_semaphore_sem_create (struct sem *f) { +/* Initialize *s; the initial value is 0. */ +bool nsync_mu_semaphore_init_sem (nsync_semaphore *s) { + struct sem *f = (struct sem *) s; int rc; int lol; f->id = 0; @@ -78,28 +68,10 @@ static bool nsync_mu_semaphore_sem_create (struct sem *f) { return true; } -static void nsync_mu_semaphore_sem_fork_child (void) { - struct sem *f; - for (f = atomic_load_explicit (&g_sems, memory_order_relaxed); f; f = f->next) { - int rc = sys_close (f->id); - STRACE ("close(%ld) → %d", f->id, rc); - ASSERT (nsync_mu_semaphore_sem_create (f)); - } -} - -static void nsync_mu_semaphore_sem_init (void) { - pthread_atfork (0, 0, nsync_mu_semaphore_sem_fork_child); -} - -/* Initialize *s; the initial value is 0. */ -bool nsync_mu_semaphore_init_sem (nsync_semaphore *s) { - static atomic_uint once; +/* Destroys *s. */ +void nsync_mu_semaphore_destroy_sem (nsync_semaphore *s) { struct sem *f = (struct sem *) s; - if (!nsync_mu_semaphore_sem_create (f)) - return false; - cosmo_once (&once, nsync_mu_semaphore_sem_init); - sems_push(f); - return true; + sys_close (f->id); } /* Wait until the count of *s exceeds 0, and decrement it. If POSIX cancellations diff --git a/third_party/nsync/panic.c b/third_party/nsync/panic.c index 2ffd4086a..7c6cebf38 100644 --- a/third_party/nsync/panic.c +++ b/third_party/nsync/panic.c @@ -28,5 +28,5 @@ void nsync_panic_ (const char *s) { "cosmoaddr2line ", program_invocation_name, " ", DescribeBacktrace (__builtin_frame_address (0)), "\n", NULL); - _Exit (44); + __builtin_trap (); } diff --git a/third_party/nsync/testing/cv_mu_timeout_stress_test.inc b/third_party/nsync/testing/cv_mu_timeout_stress_test.inc index 81a6b522a..ea9f259a9 100644 --- a/third_party/nsync/testing/cv_mu_timeout_stress_test.inc +++ b/third_party/nsync/testing/cv_mu_timeout_stress_test.inc @@ -60,7 +60,7 @@ typedef struct cv_stress_data_s { /* The delays in cv_stress_inc_loop(), cv_stress_reader_loop(), mu_stress_inc_loop(), and mu_stress_reader_loop() are uniformly distributed from 0 to STRESS_MAX_DELAY_MICROS-1 microseconds. */ -#define STRESS_MAX_DELAY_MICROS (IsNetbsd() || IsOpenbsd() ? 20000 : 4000) /* maximum delay */ +#define STRESS_MAX_DELAY_MICROS (IsNetbsd() || IsOpenbsd() ? 30000 : 4000) /* maximum delay */ #define STRESS_MEAN_DELAY_MICROS (STRESS_MAX_DELAY_MICROS / 2) /* mean delay */ #define STRESS_EXPECT_TIMEOUTS_PER_SEC (1000000 / STRESS_MEAN_DELAY_MICROS) /* expect timeouts/s*/ diff --git a/third_party/nsync/testing/cv_wait_example_test.c b/third_party/nsync/testing/cv_wait_example.c similarity index 99% rename from third_party/nsync/testing/cv_wait_example_test.c rename to third_party/nsync/testing/cv_wait_example.c index d365d4f9d..f94092987 100644 --- a/third_party/nsync/testing/cv_wait_example_test.c +++ b/third_party/nsync/testing/cv_wait_example.c @@ -25,6 +25,7 @@ #include "third_party/nsync/testing/closure.h" #include "third_party/nsync/testing/smprintf.h" #include "third_party/nsync/testing/testing.h" +#include "libc/dce.h" #include "third_party/nsync/testing/time_extra.h" /* Example use of CV.wait(): A priority queue of strings whose diff --git a/third_party/nsync/testing/note_test.c b/third_party/nsync/testing/note_test.c index 871848eca..4321c1e75 100644 --- a/third_party/nsync/testing/note_test.c +++ b/third_party/nsync/testing/note_test.c @@ -20,6 +20,7 @@ #include "third_party/nsync/testing/smprintf.h" #include "third_party/nsync/testing/testing.h" #include "third_party/nsync/testing/time_extra.h" +#include "libc/dce.h" #include "third_party/nsync/time.h" /* Verify the properties of a prenotified note. */ @@ -78,7 +79,7 @@ static void test_note_unnotified (testing t) { TEST_ERROR (t, ("timed wait on unnotified note returned too quickly (1s wait took %s)", nsync_time_str (waited, 2))); } - if (nsync_time_cmp (waited, nsync_time_ms (2000)) > 0) { + if (nsync_time_cmp (waited, nsync_time_ms (IsNetbsd() || IsOpenbsd() || IsFreebsd() ? 4000 : 2000)) > 0) { TEST_ERROR (t, ("timed wait on unnotified note returned too slowly (1s wait took %s)", nsync_time_str (waited, 2))); } diff --git a/third_party/nsync/wait_s.internal.h b/third_party/nsync/wait_s.internal.h index 3d1d1de88..a4cb868ef 100644 --- a/third_party/nsync/wait_s.internal.h +++ b/third_party/nsync/wait_s.internal.h @@ -1,6 +1,7 @@ #ifndef COSMOPOLITAN_LIBC_THREAD_WAIT_INTERNAL_H_ #define COSMOPOLITAN_LIBC_THREAD_WAIT_INTERNAL_H_ #include "libc/intrin/dll.h" +#include "third_party/nsync/defs.h" #include "third_party/nsync/atomic.h" COSMOPOLITAN_C_START_ @@ -10,17 +11,19 @@ COSMOPOLITAN_C_START_ with v pointing to the client's object and nw pointing to a struct nsync_waiter_s. */ struct nsync_waiter_s { +#if NSYNC_DEBUG uint32_t tag; /* used for debugging */ +#endif uint32_t flags; /* see below */ - struct Dll q; /* used to link children of parent */ nsync_atomic_uint32_ waiting; /* non-zero <=> the waiter is waiting */ + struct Dll q; /* used to link children of parent */ struct nsync_semaphore_s_ *sem; /* *sem will be Ved when waiter is woken */ }; /* set if waiter is embedded in Mu/CV's internal structures */ #define NSYNC_WAITER_FLAG_MUCV 0x1 -void nsync_waiter_destroy(void *); +void nsync_waiter_destroy_(void *); COSMOPOLITAN_C_END_ #endif /* COSMOPOLITAN_LIBC_THREAD_WAIT_INTERNAL_H_ */ diff --git a/third_party/openmp/kmp_lock.cpp b/third_party/openmp/kmp_lock.cpp index cc7be24da..593d805b8 100644 --- a/third_party/openmp/kmp_lock.cpp +++ b/third_party/openmp/kmp_lock.cpp @@ -23,7 +23,7 @@ #if KMP_USE_FUTEX #ifdef __COSMOPOLITAN__ -#include "third_party/nsync/futex.internal.h" +#include #else #include #include @@ -380,7 +380,7 @@ __kmp_acquire_futex_lock_timed_template(kmp_futex_lock_t *lck, kmp_int32 gtid) { long rc; #ifdef __COSMOPOLITAN__ - if ((rc = nsync_futex_wait_((int *)&(lck->lk.poll), poll_val, false, 0, NULL)) != 0) { + if ((rc = cosmo_futex_wait((int *)&(lck->lk.poll), poll_val, false, 0, NULL)) != 0) { #else if ((rc = syscall(__NR_futex, (int *)&(lck->lk.poll), FUTEX_WAIT, poll_val, NULL, NULL, 0)) != 0) { @@ -462,7 +462,7 @@ int __kmp_release_futex_lock(kmp_futex_lock_t *lck, kmp_int32 gtid) { ("__kmp_release_futex_lock: lck:%p, T#%d futex_wake 1 thread\n", lck, gtid)); #ifdef __COSMOPOLITAN__ - nsync_futex_wake_((int *)&(lck->lk.poll), 1, false); + cosmo_futex_wake((int *)&(lck->lk.poll), 1, false); #else syscall(__NR_futex, &(lck->lk.poll), FUTEX_WAKE, KMP_LOCK_BUSY(1, futex), NULL, NULL, 0); diff --git a/third_party/python/BUILD.mk b/third_party/python/BUILD.mk index 8a636844c..48b001176 100644 --- a/third_party/python/BUILD.mk +++ b/third_party/python/BUILD.mk @@ -1176,12 +1176,13 @@ THIRD_PARTY_PYTHON_STAGE2_A_DIRECTDEPS = \ LIBC_NT_KERNEL32 \ LIBC_PROC \ LIBC_RUNTIME \ - LIBC_THREAD \ LIBC_SOCK \ LIBC_STDIO \ LIBC_STR \ + LIBC_SYSTEM \ LIBC_SYSV \ LIBC_SYSV_CALLS \ + LIBC_THREAD \ LIBC_TINYMATH \ LIBC_X \ NET_HTTP \ @@ -1189,14 +1190,14 @@ THIRD_PARTY_PYTHON_STAGE2_A_DIRECTDEPS = \ THIRD_PARTY_BZIP2 \ THIRD_PARTY_GDTOA \ THIRD_PARTY_LINENOISE \ - THIRD_PARTY_MUSL \ THIRD_PARTY_MBEDTLS \ + THIRD_PARTY_MUSL \ THIRD_PARTY_PYTHON_STAGE1 \ THIRD_PARTY_SQLITE3 \ THIRD_PARTY_TZ \ - THIRD_PARTY_ZLIB \ THIRD_PARTY_XED \ - TOOL_ARGS + THIRD_PARTY_ZLIB \ + TOOL_ARGS \ THIRD_PARTY_PYTHON_STAGE2_A_DEPS = \ $(call uniq,$(foreach x,$(THIRD_PARTY_PYTHON_STAGE2_A_DIRECTDEPS),$($(x)))) diff --git a/third_party/python/Modules/socketmodule.c b/third_party/python/Modules/socketmodule.c index 03680235b..a8e368268 100644 --- a/third_party/python/Modules/socketmodule.c +++ b/third_party/python/Modules/socketmodule.c @@ -6790,24 +6790,9 @@ PyInit__socket(void) PyModule_AddIntMacro(m, MSG_TRUNC); PyModule_AddIntMacro(m, MSG_CTRUNC); PyModule_AddIntMacro(m, MSG_WAITALL); - if (MSG_DONTWAIT) PyModule_AddIntMacro(m, MSG_DONTWAIT); - if (MSG_EOR) PyModule_AddIntMacro(m, MSG_EOR); - if (MSG_NOSIGNAL) PyModule_AddIntMacro(m, MSG_NOSIGNAL); - if (MSG_BCAST) PyModule_AddIntMacro(m, MSG_BCAST); - if (MSG_MCAST) PyModule_AddIntMacro(m, MSG_MCAST); - if (MSG_CMSG_CLOEXEC) PyModule_AddIntMacro(m, MSG_CMSG_CLOEXEC); - if (MSG_ERRQUEUE) PyModule_AddIntMacro(m, MSG_ERRQUEUE); - if (MSG_CONFIRM) PyModule_AddIntMacro(m, MSG_CONFIRM); - if (MSG_MORE) PyModule_AddIntMacro(m, MSG_MORE); - if (MSG_NOTIFICATION) PyModule_AddIntMacro(m, MSG_NOTIFICATION); - if (MSG_EOF) PyModule_AddIntMacro(m, MSG_EOF); - if (MSG_FASTOPEN) PyModule_AddIntMacro(m, MSG_FASTOPEN); -#ifdef MSG_BTAG - if (MSG_BTAG) PyModule_AddIntMacro(m, MSG_BTAG); -#endif -#ifdef MSG_ETAG - if (MSG_ETAG) PyModule_AddIntMacro(m, MSG_ETAG); -#endif + PyModule_AddIntMacro(m, MSG_DONTWAIT); + PyModule_AddIntMacro(m, MSG_NOSIGNAL); + if (MSG_FASTOPEN != -1) PyModule_AddIntMacro(m, MSG_FASTOPEN); /* Protocol level and numbers, usable for [gs]etsockopt */ PyModule_AddIntMacro(m, SOL_SOCKET); diff --git a/third_party/sed/defs.h b/third_party/sed/defs.h index 54ac79922..84cba2337 100644 --- a/third_party/sed/defs.h +++ b/third_party/sed/defs.h @@ -3,6 +3,7 @@ #include "libc/calls/typedef/u.h" #include "libc/limits.h" #include "third_party/regex/regex.h" +#include "third_party/sed/shade.h" COSMOPOLITAN_C_START_ /* diff --git a/third_party/sed/extern.h b/third_party/sed/extern.h index 0c190c9bc..fbeb0497a 100644 --- a/third_party/sed/extern.h +++ b/third_party/sed/extern.h @@ -4,6 +4,7 @@ #include "libc/stdio/stdio.h" #include "third_party/regex/regex.h" #include "third_party/sed/defs.h" +#include "third_party/sed/shade.h" COSMOPOLITAN_C_START_ extern struct s_command *prog; diff --git a/third_party/sed/shade.h b/third_party/sed/shade.h new file mode 100644 index 000000000..ceea8d4b5 --- /dev/null +++ b/third_party/sed/shade.h @@ -0,0 +1,31 @@ +#ifndef COSMOPOLITAN_THIRD_PARTY_SED_SHADE_H_ +#define COSMOPOLITAN_THIRD_PARTY_SED_SHADE_H_ + +#define prog _sed_prog +#define appends_ _sed_appends_ +#define g_match _sed_g_match +#define maxnsub _sed_maxnsub +#define linenum _sed_linenum +#define appendnum _sed_appendnum +#define aflag _sed_aflag +#define eflag _sed_eflag +#define nflag _sed_nflag +#define fname _sed_fname +#define outfname _sed_outfname +#define infile _sed_infile +#define outfile _sed_outfile +#define rflags _sed_rflags +#define cfclose _sed_cfclose +#define compile _sed_compile +#define cspace _sed_cspace +#define cu_fgets _sed_cu_fgets +#define mf_fgets _sed_mf_fgets +#define lastline _sed_lastline +#define process _sed_process +#define resetstate _sed_resetstate +#define strregerror _sed_strregerror +#define xmalloc _sed_xmalloc +#define xrealloc _sed_xrealloc +#define xcalloc _sed_xcalloc + +#endif /* COSMOPOLITAN_THIRD_PARTY_SED_SHADE_H_ */ diff --git a/third_party/sqlite3/BUILD.mk b/third_party/sqlite3/BUILD.mk index 3410c4975..3ea4086fa 100644 --- a/third_party/sqlite3/BUILD.mk +++ b/third_party/sqlite3/BUILD.mk @@ -52,6 +52,7 @@ THIRD_PARTY_SQLITE3_A_DIRECTDEPS = \ LIBC_RUNTIME \ LIBC_STDIO \ LIBC_STR \ + LIBC_SYSTEM \ LIBC_SYSV \ LIBC_SYSV_CALLS \ LIBC_THREAD \ @@ -62,7 +63,7 @@ THIRD_PARTY_SQLITE3_A_DIRECTDEPS = \ THIRD_PARTY_MUSL \ THIRD_PARTY_TZ \ THIRD_PARTY_ZLIB \ - TOOL_ARGS + TOOL_ARGS \ THIRD_PARTY_SQLITE3_A_DEPS := \ $(call uniq,$(foreach x,$(THIRD_PARTY_SQLITE3_A_DIRECTDEPS),$($(x)))) diff --git a/third_party/tr/extern.h b/third_party/tr/extern.h index 31b1ed884..14995931e 100644 --- a/third_party/tr/extern.h +++ b/third_party/tr/extern.h @@ -3,6 +3,8 @@ #include "libc/limits.h" COSMOPOLITAN_C_START_ +#define next _tr_next + typedef struct { enum { STRING1, STRING2 } which; enum { EOS, INFINITE, NORMAL, RANGE, SEQUENCE, SET } state; diff --git a/third_party/tr/tr.c b/third_party/tr/tr.c index fd1fe6e11..d9c7f572a 100644 --- a/third_party/tr/tr.c +++ b/third_party/tr/tr.c @@ -42,8 +42,8 @@ #include "third_party/tr/cmd.h" #include "third_party/tr/extern.h" -int delete[NCHARS], squeeze[NCHARS]; -int translate[NCHARS] = { +static int delete[NCHARS], squeeze[NCHARS]; +static int translate[NCHARS] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* ASCII */ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, @@ -78,8 +78,8 @@ int translate[NCHARS] = { 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, }; -STR s1 = { STRING1, NORMAL, 0, OOBCH, { 0, OOBCH }, NULL, NULL }; -STR s2 = { STRING2, NORMAL, 0, OOBCH, { 0, OOBCH }, NULL, NULL }; +static STR s1 = { STRING1, NORMAL, 0, OOBCH, { 0, OOBCH }, NULL, NULL }; +static STR s2 = { STRING2, NORMAL, 0, OOBCH, { 0, OOBCH }, NULL, NULL }; static void setup(int *, char *, STR *, int); static void usage(void); @@ -90,9 +90,6 @@ _tr(int argc, char *argv[]) int ch, cnt, lastch, *p; int cflag, dflag, sflag; - if (pledge("stdio", NULL) == -1) - err(1, "pledge"); - cflag = dflag = sflag = 0; while ((ch = getopt(argc, argv, "Ccds")) != -1) switch(ch) { diff --git a/third_party/tz/localtime.c b/third_party/tz/localtime.c index 06139e49f..34f7cf648 100644 --- a/third_party/tz/localtime.c +++ b/third_party/tz/localtime.c @@ -2,6 +2,10 @@ │ vi: set noet ft=c ts=8 sw=8 fenc=utf-8 :vi │ ╚─────────────────────────────────────────────────────────────────────────────*/ #define LOCALTIME_IMPLEMENTATION +#include "lock.h" +#include "tzdir.h" +#include "tzfile.h" +#include "private.h" #include "libc/calls/blockcancel.internal.h" #include "libc/calls/calls.h" #include "libc/cxxabi.h" @@ -10,20 +14,15 @@ #include "libc/serialize.h" #include "libc/str/str.h" #include "libc/sysv/consts/o.h" -#include "libc/thread/thread.h" -#include "libc/thread/tls.h" #include "libc/time.h" #include "libc/inttypes.h" #include "libc/sysv/consts/ok.h" #include "libc/runtime/runtime.h" #include "libc/stdckdint.h" #include "libc/time.h" -#include "tzdir.h" -#include "tzfile.h" #include "libc/nt/struct/timezoneinformation.h" #include "libc/nt/time.h" #include "libc/dce.h" -#include "private.h" /* Convert timestamp from time_t to struct tm. */ @@ -624,34 +623,10 @@ localtime_windows_init(void) setenv("TZ", buf, true); } -static pthread_mutex_t locallock = PTHREAD_MUTEX_INITIALIZER; - -static dontinline void -localtime_wipe(void) -{ - pthread_mutex_init(&locallock, 0); -} - -static dontinline void -localtime_lock(void) -{ - pthread_mutex_lock(&locallock); -} - -static dontinline void -localtime_unlock(void) -{ - pthread_mutex_unlock(&locallock); -} - __attribute__((__constructor__(80))) textstartup static void localtime_init(void) { - localtime_wipe(); - pthread_atfork(localtime_lock, - localtime_unlock, - localtime_wipe); if (IsWindows()) localtime_windows_init(); } @@ -2052,9 +2027,9 @@ localtime_tzset_unlocked(void) void tzset(void) { - localtime_lock(); + __localtime_lock(); localtime_tzset_unlocked(); - localtime_unlock(); + __localtime_unlock(); } static void @@ -2067,7 +2042,7 @@ static void localtime_gmtcheck(void) { static bool gmt_is_set; - localtime_lock(); + __localtime_lock(); if (! gmt_is_set) { #ifdef ALL_STATE gmtptr = malloc(sizeof *gmtptr); @@ -2077,7 +2052,7 @@ localtime_gmtcheck(void) localtime_gmtload(gmtptr); gmt_is_set = true; } - localtime_unlock(); + __localtime_unlock(); } /* @@ -2193,11 +2168,11 @@ localsub(struct state const *sp, time_t const *timep, int_fast32_t setname, static struct tm * localtime_tzset(time_t const *timep, struct tm *tmp, bool setname) { - localtime_lock(); + __localtime_lock(); if (setname || !lcl_is_set) localtime_tzset_unlocked(); tmp = localsub(lclptr, timep, setname, tmp); - localtime_unlock(); + __localtime_unlock(); return tmp; } @@ -2834,10 +2809,10 @@ time_t mktime(struct tm *tmp) { time_t t; - localtime_lock(); + __localtime_lock(); localtime_tzset_unlocked(); t = mktime_tzname(lclptr, tmp, true); - localtime_unlock(); + __localtime_unlock(); return t; } diff --git a/third_party/tz/lock.h b/third_party/tz/lock.h new file mode 100644 index 000000000..501505478 --- /dev/null +++ b/third_party/tz/lock.h @@ -0,0 +1,11 @@ +#ifndef COSMOPOLITAN_THIRD_PARTY_TZ_LOCK_H_ +#define COSMOPOLITAN_THIRD_PARTY_TZ_LOCK_H_ +#include "libc/thread/thread.h" +COSMOPOLITAN_C_START_ + +void __localtime_lock(void); +void __localtime_unlock(void); +void __localtime_wipe(void); + +COSMOPOLITAN_C_END_ +#endif /* COSMOPOLITAN_THIRD_PARTY_TZ_LOCK_H_ */ diff --git a/third_party/zip/BUILD.mk b/third_party/zip/BUILD.mk index a2567c970..1a4ab4c20 100644 --- a/third_party/zip/BUILD.mk +++ b/third_party/zip/BUILD.mk @@ -85,10 +85,11 @@ THIRD_PARTY_ZIP_DIRECTDEPS = \ LIBC_LOG \ LIBC_MEM \ LIBC_NEXGEN32E \ + LIBC_PROC \ LIBC_RUNTIME \ LIBC_STDIO \ - LIBC_PROC \ LIBC_STR \ + LIBC_SYSTEM \ LIBC_SYSV \ LIBC_X \ THIRD_PARTY_BZIP2 \ diff --git a/third_party/zlib/crc_folding.c b/third_party/zlib/crc_folding.c index 2702f909a..cc2fa875a 100644 --- a/third_party/zlib/crc_folding.c +++ b/third_party/zlib/crc_folding.c @@ -406,7 +406,7 @@ partial: } #endif - _mm_storeu_si128((__m128i *)dst, xmm_crc_part); + memcpy(dst, src, len); /* TODO: Possibly generate more efficient code. */ partial_fold(s, len, &xmm_crc0, &xmm_crc1, &xmm_crc2, &xmm_crc3, &xmm_crc_part); done: diff --git a/third_party/zlib/deflate.c b/third_party/zlib/deflate.c index 58f9474e7..64ebcddc4 100644 --- a/third_party/zlib/deflate.c +++ b/third_party/zlib/deflate.c @@ -229,6 +229,8 @@ int ZEXPORT deflateInit(strm, level) /* To do: ignore strm->next_in if we use it as window */ } +#define WINDOW_PADDING 8 + /* ========================================================================= */ int ZEXPORT deflateInit2(strm, level, method, windowBits, memLevel, strategy) z_streamp strm; @@ -238,7 +240,6 @@ int ZEXPORT deflateInit2(strm, level, method, windowBits, memLevel, strategy) int memLevel; int strategy; { - unsigned window_padding = 8; deflate_state *s; int wrap = 1; @@ -325,12 +326,12 @@ int ZEXPORT deflateInit2(strm, level, method, windowBits, memLevel, strategy) s->hash_shift = ((s->hash_bits + MIN_MATCH-1) / MIN_MATCH); s->window = (Bytef *) ZALLOC(strm, - s->w_size + window_padding, + s->w_size + WINDOW_PADDING, 2*sizeof(Byte)); /* Avoid use of unitialized values in the window, see crbug.com/1137613 and * crbug.com/1144420 */ if (s->window) { /* [jart] fix regression in malloc failure checking */ - zmemzero(s->window, (s->w_size + window_padding) * (2 * sizeof(Byte))); + zmemzero(s->window, (s->w_size + WINDOW_PADDING) * (2 * sizeof(Byte))); } s->prev = (Posf *) ZALLOC(strm, s->w_size, sizeof(Pos)); /* Avoid use of uninitialized value, see: @@ -770,6 +771,12 @@ uLong ZEXPORT deflateBound(strm, sourceLen) wraplen = 6; } + /* With Chromium's hashing, s->hash_bits may not correspond to the + memLevel, making the computations below incorrect. Return the + conservative bound. */ + if (s->chromium_zlib_hash) + return (fixedlen > storelen ? fixedlen : storelen) + wraplen; + /* if not default parameters, return one of the conservative bounds */ if (s->w_bits != 15 || s->hash_bits != 8 + 7) return (s->w_bits <= s->hash_bits ? fixedlen : storelen) + wraplen; @@ -1199,7 +1206,9 @@ int ZEXPORT deflateCopy(dest, source) zmemcpy((voidpf)ds, (voidpf)ss, sizeof(deflate_state)); ds->strm = dest; - ds->window = (Bytef *) ZALLOC(dest, ds->w_size, 2*sizeof(Byte)); + ds->window = (Bytef *) ZALLOC(dest, + ds->w_size + WINDOW_PADDING, + 2*sizeof(Byte)); ds->prev = (Posf *) ZALLOC(dest, ds->w_size, sizeof(Pos)); ds->head = (Posf *) ZALLOC(dest, ds->hash_size, sizeof(Pos)); ds->pending_buf = (uchf *) ZALLOC(dest, ds->lit_bufsize, 4); @@ -1210,7 +1219,8 @@ int ZEXPORT deflateCopy(dest, source) return Z_MEM_ERROR; } /* following zmemcpy do not work for 16-bit MSDOS */ - zmemcpy(ds->window, ss->window, ds->w_size * 2 * sizeof(Byte)); + zmemcpy(ds->window, ss->window, + (ds->w_size + WINDOW_PADDING) * 2 * sizeof(Byte)); zmemcpy((voidpf)ds->prev, (voidpf)ss->prev, ds->w_size * sizeof(Pos)); zmemcpy((voidpf)ds->head, (voidpf)ss->head, ds->hash_size * sizeof(Pos)); zmemcpy(ds->pending_buf, ss->pending_buf, (uInt)ds->pending_buf_size); diff --git a/third_party/zlib/inflate.c b/third_party/zlib/inflate.c index 75fa6b56e..acc88d1dc 100644 --- a/third_party/zlib/inflate.c +++ b/third_party/zlib/inflate.c @@ -256,6 +256,8 @@ int value; struct inflate_state FAR *state; if (inflateStateCheck(strm)) return Z_STREAM_ERROR; + if (bits == 0) + return Z_OK; state = (struct inflate_state FAR *)strm->state; if (bits < 0) { state->hold = 0; @@ -1480,7 +1482,7 @@ z_streamp strm; /* if first time, start search in bit buffer */ if (state->mode != SYNC) { state->mode = SYNC; - state->hold <<= state->bits & 7; + state->hold >>= state->bits & 7; state->bits -= state->bits & 7; len = 0; while (state->bits >= 8) { @@ -1551,8 +1553,9 @@ z_streamp source; if (copy == Z_NULL) return Z_MEM_ERROR; window = Z_NULL; if (state->window != Z_NULL) { - window = (unsigned char FAR *) - ZALLOC(source, 1U << state->wbits, sizeof(unsigned char)); + window = (unsigned char FAR *)ZALLOC( + source, (1U << state->wbits) + CHUNKCOPY_CHUNK_SIZE, + sizeof(unsigned char)); if (window == Z_NULL) { ZFREE(source, copy); return Z_MEM_ERROR; diff --git a/tool/args/BUILD.mk b/tool/args/BUILD.mk index 40aca11cd..1ed3f664a 100644 --- a/tool/args/BUILD.mk +++ b/tool/args/BUILD.mk @@ -16,6 +16,7 @@ TOOL_ARGS_A_CHECKS = \ $(TOOL_ARGS_A).pkg TOOL_ARGS_A_DIRECTDEPS = \ + LIBC_CALLS \ LIBC_INTRIN \ LIBC_MEM \ LIBC_NEXGEN32E \ diff --git a/tool/args/args.c b/tool/args/args.c index 2a6c0dc44..380f70165 100644 --- a/tool/args/args.c +++ b/tool/args/args.c @@ -133,6 +133,7 @@ int LoadZipArgsImpl(int *argc, char ***argv, char *data) { * replaced with whatever CLI args were specified by the user. * * @return 0 on success, or -1 if not found w/o errno clobber + * @deprecated please use `cosmo_args()` it's more powerful */ int LoadZipArgs(int *argc, char ***argv) { int e; diff --git a/tool/args/args2.c b/tool/args/args2.c new file mode 100644 index 000000000..6af066c4f --- /dev/null +++ b/tool/args/args2.c @@ -0,0 +1,582 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 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/cosmo.h" +#include "libc/ctype.h" +#include "libc/cxxabi.h" +#include "libc/errno.h" +#include "libc/mem/mem.h" +#include "libc/runtime/runtime.h" +#include "libc/str/tab.h" +#include "libc/sysv/consts/o.h" + +__static_yoink("zipos"); + +#define CLEAR(b) \ + do { \ + b##_cap = 8; \ + b##_len = 0; \ + if (!(b##_ptr = calloc(b##_cap, sizeof(*b##_ptr)))) \ + goto Failure; \ + } while (0) + +#define APPEND(b, c) \ + do { \ + if (b##_len + 2 > b##_cap) { \ + b##_cap += b##_cap >> 1; \ + void *p_ = realloc(b##_ptr, b##_cap * sizeof(*b##_ptr)); \ + if (!p_) \ + goto Failure; \ + b##_ptr = p_; \ + } \ + b##_ptr[b##_len++] = c; \ + b##_ptr[b##_len] = 0; \ + } while (0) + +#define APPEND_DUP(b, s) \ + do { \ + char *tmp; \ + if (!(tmp = strdup(s))) \ + goto Failure; \ + APPEND(args, tmp); \ + } while (0) + +static int esc(int c) { + switch (c) { + case 'a': + return '\a'; + case 'b': + return '\b'; + case 't': + return '\t'; + case 'n': + return '\n'; + case 'v': + return '\v'; + case 'f': + return '\f'; + case 'r': + return '\r'; + case 'e': + return '\e'; + default: + return -1; + } +} + +static void cosmo_args_free(void *list) { + char **args = list; + char *arg; + while ((arg = *args++)) + free(arg); + free(list); +} + +/** + * Replaces argument list with `/zip/.args` contents if it exists. + * + * First read the documentation to LoadZipArgs(). This works basically + * the same, assuming you pass `"/zip/.args"` as the first argument. The + * difference is that arguments here are parsed more similarly to the + * shell. In the old version, if you wanted your zip .args config to + * insert three arguments at the beginning of your argv, you'd say: + * + * arg1 + * arg2 + * arg3 + * + * This will still work. You can also now say: + * + * arg1 arg2 + * arg3 + * + * This breaks backwards compatibility, since the old design was made + * for programs like ar.ape that wanted to be able to accept filename + * arguments that could potentially have spaces. This new parser, on the + * other hand, is designed to help offer the configurability a project + * like llamafile needs, without going so far as to be Turing complete. + * For example, you could say: + * + * # this is a comment + * this\ is' a single arg'"ok"# # comment + * + * Which will result in the C string `"this is a single argok#"`. You + * can even use $VAR notation to schlep in environment variables. Here's + * how this is different from shell: + * + * 1. We don't expand $foo into multiple arguments if it has spaces + * 2. Double quoted strings work like they do in C, e.g. `"\177\x7f\n"` + * 3. You can't recursively reference environment variables + * + * If the process was started in a degenerate state without argv[0] then + * GetProgramExecutableName() will be inserted in its place, on success. + * + * The `path` argument may be null, in which case only normalization is + * performed. It is not considered an error if `path` is specified and + * the file doesn't exist. The errno state will be left dirty if that + * happens, so it can be checked by clearing `errno` before calling. + * + * The returned memory is copied and automatically freed on exit(). + * + * @return argc on success, or -1 w/ errno + */ +int cosmo_args(const char *path, char ***argv) { + + // the file + int fd = -1; + + // variable name builder + int var_cap = 0; + int var_len = 0; + char *var_ptr = 0; + + // argument string builder + int arg_cap = 0; + int arg_len = 0; + char *arg_ptr = 0; + + // argument array builder + int args_cap = 0; + int args_len = 0; + char **args_ptr = 0; + + // initialize memory + CLEAR(var); + CLEAR(arg); + CLEAR(args); + + // state machine + enum { + NORMAL, + COMMENT, + ARGUMENT, + BACKSLASH, + DOLLAR, + DOLLAR_VAR, + DOLLAR_LCB, + DOT, + DOT_DOT, + DOT_DOT_DOT, + QUOTE, + DQUOTE, + DQUOTE_DOLLAR, + DQUOTE_DOLLAR_VAR, + DQUOTE_DOLLAR_LCB, + DQUOTE_BACKSLASH, + DQUOTE_BACKSLASH_X, + DQUOTE_BACKSLASH_X_XDIGIT, + DQUOTE_BACKSLASH_DIGIT, + DQUOTE_BACKSLASH_DIGIT_DIGIT, + } t = NORMAL; + + // extra state + int x, numba = 0; + + // add program argument + char **argvp = *argv; + if (*argvp) { + APPEND_DUP(args, *argvp++); + } else { + APPEND_DUP(args, GetProgramExecutableName()); + } + + // perform i/o + if (path) { + if ((fd = open(path, O_RDONLY)) == -1) + if (errno != ENOENT && errno != ENOTDIR) + goto Failure; + if (fd != -1) { + for (;;) { + char buf[512]; + int got = read(fd, buf, sizeof(buf)); + if (got == -1) + goto Failure; + if (!got) + break; + for (int i = 0; i < got; ++i) { + int c = buf[i] & 255; + switch (t) { + + case NORMAL: + switch (c) { + case ' ': + case '\t': + case '\r': + case '\n': + case '\f': + case '\v': + break; + case '#': + t = COMMENT; + break; + case '\'': + t = QUOTE; + break; + case '"': + t = DQUOTE; + break; + case '$': + t = DOLLAR; + break; + case '.': + t = DOT; + break; + case '\\': + t = BACKSLASH; + break; + default: + APPEND(arg, c); + t = ARGUMENT; + break; + } + break; + + Argument: + case ARGUMENT: + switch (c) { + case ' ': + case '\t': + case '\r': + case '\n': + case '\f': + case '\v': + APPEND(args, arg_ptr); + CLEAR(arg); + t = NORMAL; + break; + case '\'': + t = QUOTE; + break; + case '"': + t = DQUOTE; + break; + case '$': + t = DOLLAR; + break; + case '\\': + t = BACKSLASH; + break; + default: + APPEND(arg, c); + break; + } + break; + + case BACKSLASH: + if (c == '\r') { + // do nothing + } else if (c == '\n') { + t = NORMAL; + } else if ((x = esc(c)) != -1) { + APPEND(arg, x); + t = ARGUMENT; + } else { + APPEND(arg, c); + t = ARGUMENT; + } + break; + + case COMMENT: + if (c == '\n') + t = NORMAL; + break; + + case DOLLAR: + if (isalnum(c) || c == '_') { + APPEND(var, c); + t = DOLLAR_VAR; + } else if (c == '{') { + t = DOLLAR_LCB; + } else { + APPEND(arg, '$'); + if (c != '$') { + t = ARGUMENT; + goto Argument; + } + } + break; + + case DOLLAR_VAR: + if (isalnum(c) || c == '_') { + APPEND(var, c); + } else { + char *val = getenv(var_ptr); + if (!val) + val = ""; + free(var_ptr); + CLEAR(var); + while (*val) + APPEND(arg, *val++); + t = ARGUMENT; + goto Argument; + } + break; + + case DOLLAR_LCB: + if (c == '}') { + char *val = getenv(var_ptr); + if (!val) + val = ""; + free(var_ptr); + CLEAR(var); + while (*val) + APPEND(arg, *val++); + t = ARGUMENT; + } else { + APPEND(var, c); + } + break; + + case QUOTE: + if (c == '\'') { + t = ARGUMENT; + } else { + APPEND(arg, c); + } + break; + + Dquote: + case DQUOTE: + if (c == '"') { + t = ARGUMENT; + } else if (c == '$') { + t = DQUOTE_DOLLAR; + } else if (c == '\\') { + t = DQUOTE_BACKSLASH; + } else { + APPEND(arg, c); + } + break; + + case DQUOTE_DOLLAR: + if (isalnum(c) || c == '_') { + APPEND(var, c); + t = DQUOTE_DOLLAR_VAR; + } else if (c == '{') { + t = DQUOTE_DOLLAR_LCB; + } else { + APPEND(arg, '$'); + if (c != '$') { + t = DQUOTE; + goto Dquote; + } + } + break; + + case DQUOTE_DOLLAR_VAR: + if (isalnum(c) || c == '_') { + APPEND(var, c); + } else { + char *val = getenv(var_ptr); + if (!val) + val = ""; + free(var_ptr); + CLEAR(var); + while (*val) + APPEND(arg, *val++); + t = DQUOTE; + goto Dquote; + } + break; + + case DQUOTE_DOLLAR_LCB: + if (c == '}') { + char *val = getenv(var_ptr); + if (!val) + val = ""; + free(var_ptr); + CLEAR(var); + while (*val) + APPEND(arg, *val++); + t = DQUOTE; + } else { + APPEND(var, c); + } + break; + + case DQUOTE_BACKSLASH: + if (isdigit(c)) { + numba = c - '0'; + t = DQUOTE_BACKSLASH_DIGIT; + } else if (c == 'x') { + t = DQUOTE_BACKSLASH_X; + } else if ((x = esc(c)) != -1) { + APPEND(arg, x); + t = DQUOTE; + } else if (c == '\r') { + // do nothing + } else if (c == '\n') { + t = DQUOTE; + } else { + APPEND(arg, c); + t = DQUOTE; + } + break; + + case DQUOTE_BACKSLASH_DIGIT: + if (isdigit(c)) { + numba <<= 3; + numba += c - '0'; + t = DQUOTE_BACKSLASH_DIGIT_DIGIT; + } else { + APPEND(arg, numba); + t = DQUOTE; + goto Dquote; + } + break; + + case DQUOTE_BACKSLASH_DIGIT_DIGIT: + if (isdigit(c)) { + numba <<= 3; + numba += c - '0'; + APPEND(arg, numba); + t = DQUOTE; + } else { + APPEND(arg, numba); + t = DQUOTE; + goto Dquote; + } + break; + + case DQUOTE_BACKSLASH_X: + if ((x = kHexToInt[c]) != -1) { + numba = x; + t = DQUOTE_BACKSLASH_X_XDIGIT; + } else { + APPEND(arg, 'x'); + t = DQUOTE; + goto Dquote; + } + break; + + case DQUOTE_BACKSLASH_X_XDIGIT: + if ((x = kHexToInt[c]) != -1) { + numba <<= 4; + numba += x; + APPEND(arg, numba); + t = DQUOTE; + } else { + APPEND(arg, numba); + t = DQUOTE; + goto Dquote; + } + break; + + case DOT: + if (c == '.') { + t = DOT_DOT; + } else { + APPEND(arg, '.'); + t = ARGUMENT; + goto Argument; + } + break; + + case DOT_DOT: + if (c == '.') { + t = DOT_DOT_DOT; + } else { + APPEND(arg, '.'); + APPEND(arg, '.'); + t = ARGUMENT; + goto Argument; + } + break; + + case DOT_DOT_DOT: + if (isspace(c)) { + while (*argvp) + APPEND_DUP(args, *argvp++); + t = NORMAL; + } else { + APPEND(arg, '.'); + APPEND(arg, '.'); + APPEND(arg, '.'); + t = ARGUMENT; + goto Argument; + } + break; + + default: + __builtin_unreachable(); + } + } + } + if (close(fd)) + goto Failure; + + // clean up dirty state + switch (t) { + case DOT: + APPEND(arg, '.'); + break; + case DOT_DOT: + APPEND(arg, '.'); + APPEND(arg, '.'); + break; + case DOT_DOT_DOT: + while (*argvp) + APPEND_DUP(args, *argvp++); + break; + case DOLLAR: + APPEND(arg, '$'); + break; + case DOLLAR_VAR: + case DQUOTE_DOLLAR_VAR: + char *val = getenv(var_ptr); + if (!val) + val = ""; + while (*val) + APPEND(arg, *val++); + break; + case DOLLAR_LCB: + case DQUOTE_DOLLAR_LCB: + APPEND(arg, '$'); + APPEND(arg, '{'); + for (int j = 0; var_ptr[j]; ++j) + APPEND(arg, var_ptr[j]); + break; + default: + break; + } + if (arg_len) { + APPEND(args, arg_ptr); + CLEAR(arg); + } + } + } + + // append original argv if ... wasn't specified + while (*argvp) + APPEND_DUP(args, *argvp++); + + // return result + __cxa_atexit(cosmo_args_free, args_ptr, 0); + *argv = args_ptr; + free(arg_ptr); + free(var_ptr); + return args_len; + +Failure: + cosmo_args_free(args_ptr); + free(arg_ptr); + if (fd != -1) + close(fd); + return -1; +} diff --git a/tool/build/BUILD.mk b/tool/build/BUILD.mk index 1f939f4b1..2d37a2bd0 100644 --- a/tool/build/BUILD.mk +++ b/tool/build/BUILD.mk @@ -42,6 +42,7 @@ TOOL_BUILD_DIRECTDEPS = \ LIBC_SOCK \ LIBC_STDIO \ LIBC_STR \ + LIBC_SYSTEM \ LIBC_SYSV \ LIBC_SYSV_CALLS \ LIBC_THREAD \ @@ -60,7 +61,7 @@ TOOL_BUILD_DIRECTDEPS = \ THIRD_PARTY_XED \ THIRD_PARTY_ZLIB \ THIRD_PARTY_ZLIB_GZ \ - TOOL_BUILD_LIB + TOOL_BUILD_LIB \ TOOL_BUILD_DEPS := \ $(call uniq,$(foreach x,$(TOOL_BUILD_DIRECTDEPS),$($(x)))) @@ -137,8 +138,8 @@ o/$(MODE)/tool/build/dso/dlopen_helper.so: \ o/$(MODE)/tool/build/dso/dlopen_helper.o \ $(OUTPUT_OPTION) -o/$(MODE)/tool/build/dlopen_test.runs: \ - o/$(MODE)/tool/build/dlopen_test \ +o/$(MODE)/tool/build/dlopen_tester.runs: \ + o/$(MODE)/tool/build/dlopen_tester \ o/$(MODE)/tool/build/dso/dlopen_helper.so $< o/$(MODE)/tool/build/dso/dlopen_helper.so diff --git a/tool/build/apelink.c b/tool/build/apelink.c index 862ed3f13..f84b50ef2 100644 --- a/tool/build/apelink.c +++ b/tool/build/apelink.c @@ -85,6 +85,13 @@ " executable will self-modify its header on\n" \ " the first run, to use the platform format\n" \ "\n" \ + " -k KERNEL test for maching kernel name [repeatable]\n" \ + " when set, the shell script for subsequent\n" \ + " loader executables will check if uname -s\n" \ + " output matches the kernel string, only if\n" \ + " the loader executable architecture is not\n" \ + " an architecture in the input binary list\n" \ + "\n" \ " -M PATH bundle ape loader source code file for m1\n" \ " processors running the xnu kernel so that\n" \ " it can be compiled on the fly by xcode\n" \ @@ -213,6 +220,7 @@ struct Loader { char *ddarg_size1; char *ddarg_skip2; char *ddarg_size2; + const char *kernel; }; struct Loaders { @@ -244,6 +252,7 @@ static struct Inputs inputs; static char ape_heredoc[15]; static enum Strategy strategy; static struct Loaders loaders; +static const char *loader_kernel; static const char *custom_sh_code; static bool force_bypass_binfmt_misc; static bool generate_debuggable_binary; @@ -979,13 +988,19 @@ static void AddLoader(const char *path) { if (loaders.n == ARRAYLEN(loaders.p)) { Die(prog, "too many loaders"); } - loaders.p[loaders.n++].path = path; + struct Loader *loader = &loaders.p[loaders.n++]; + loader->path = path; + loader->kernel = loader_kernel; +} + +static void SetLoaderKernel(const char *kernel) { + loader_kernel = kernel; } static void GetOpts(int argc, char *argv[]) { int opt, bits; bool got_support_vector = false; - while ((opt = getopt(argc, argv, "hvgsGBo:l:S:M:V:")) != -1) { + while ((opt = getopt(argc, argv, "hvgsGBo:l:k:S:M:V:")) != -1) { switch (opt) { case 'o': outpath = optarg; @@ -1009,6 +1024,10 @@ static void GetOpts(int argc, char *argv[]) { HashInputString("-l"); AddLoader(optarg); break; + case 'k': + HashInputString("-k"); + SetLoaderKernel(optarg); + break; case 'S': HashInputString("-S"); HashInputString(optarg); @@ -1630,6 +1649,28 @@ static char *GenerateScriptIfMachine(char *p, struct Input *in) { } } +static char *GenerateScriptIfLoaderMachine(char *p, struct Loader *loader) { + if (loader->machine == EM_NEXGEN32E) { + p = stpcpy(p, "if [ \"$m\" = x86_64 ] || [ \"$m\" = amd64 ]"); + } else if (loader->machine == EM_AARCH64) { + p = stpcpy(p, "if [ \"$m\" = aarch64 ] || [ \"$m\" = arm64 ] || [ \"$m\" = evbarm ]"); + } else if (loader->machine == EM_PPC64) { + p = stpcpy(p, "if [ \"$m\" = ppc64le ]"); + } else if (loader->machine == EM_MIPS) { + p = stpcpy(p, "if [ \"$m\" = mips64 ]"); + } else { + Die(loader->path, "unsupported cpu architecture"); + } + + if (loader->kernel) { + p = stpcpy(p, " && [ \"$k\" = "); + p = stpcpy(p, loader->kernel); + p = stpcpy(p, " ]"); + } + + return stpcpy(p, "; then\n"); +} + static char *FinishGeneratingDosHeader(char *p) { p = WRITE16LE(p, 0x1000); // 10: MZ: lowers upper bound load / 16 p = WRITE16LE(p, 0xf800); // 12: MZ: roll greed on bss @@ -1879,7 +1920,15 @@ int main(int argc, char *argv[]) { for (j = i + 1; j < loaders.n; ++j) { if (loaders.p[i].os == loaders.p[j].os && loaders.p[i].machine == loaders.p[j].machine) { - Die(prog, "multiple ape loaders specified for the same platform"); + if (!loaders.p[i].kernel && !loaders.p[j].kernel) { + Die(prog, "multiple ape loaders specified for the same platform"); + } + if (loaders.p[i].kernel != NULL && + loaders.p[j].kernel != NULL && + strcmp(loaders.p[i].kernel, loaders.p[j].kernel) == 0) { + Die(prog, "multiple ape loaders specified for the same platform " + "with matching kernels"); + } } } } @@ -2190,6 +2239,36 @@ int main(int argc, char *argv[]) { gotsome = true; } } + + // extract the ape loader for non-input architectures + // if the user requested a host kernel check, get the host kernel + if (loader_kernel) { + p = stpcpy(p, "k=$(uname -s 2>/dev/null) || k=unknown\n"); + } + for (i = 0; i < loaders.n; ++i) { + struct Loader *loader = loaders.p + i; + if (loader->used) { + continue; + } + loader->used = true; + p = GenerateScriptIfLoaderMachine(p, loader); + p = stpcpy(p, "mkdir -p \"${t%/*}\" ||exit\n" + "dd if=\"$o\""); + p = stpcpy(p, " skip="); + loader->ddarg_skip2 = p; + p = GenerateDecimalOffsetRelocation(p); + p = stpcpy(p, " count="); + loader->ddarg_size2 = p; + p = GenerateDecimalOffsetRelocation(p); + p = stpcpy(p, " bs=1 2>/dev/null | gzip -dc >\"$t.$$\" ||exit\n" + "chmod 755 \"$t.$$\" ||exit\n" + "mv -f \"$t.$$\" \"$t\" ||exit\n"); + p = stpcpy(p, "exec \"$t\" \"$o\" \"$@\"\n" + "fi\n"); + gotsome = true; + } + + // close if-statements if (inputs.n && (support_vector & _HOSTXNU)) { if (!gotsome) { p = stpcpy(p, "true\n"); diff --git a/tool/build/ar.c b/tool/build/ar.c index 28cd53b8f..29f9abb32 100644 --- a/tool/build/ar.c +++ b/tool/build/ar.c @@ -298,6 +298,7 @@ static void CopyFileOrDie(const char *inpath, int infd, // if (got != -1) { exchanged = got; } else if (errno == EXDEV || // different partitions + errno == EINVAL || // possible w/ ecryptfs errno == ENOSYS || // not linux or freebsd errno == ENOTSUP || // probably a /zip file errno == EOPNOTSUPP) { // technically the same diff --git a/tool/build/compile.c b/tool/build/compile.c index 4f0f682d9..2ade1ce7b 100644 --- a/tool/build/compile.c +++ b/tool/build/compile.c @@ -798,7 +798,11 @@ bool MovePreservingDestinationInode(const char *from, const char *to) { rc = copy_file_range(fdin, 0, fdout, 0, remain, 0); if (rc != -1) { remain -= rc; - } else if (errno == EXDEV || errno == ENOSYS) { + } else if (errno == EXDEV || // different partitions + errno == EINVAL || // possible w/ ecryptfs + errno == ENOSYS || // not linux or freebsd + errno == ENOTSUP || // no fs support for it + errno == EOPNOTSUPP) { if (lseek(fdin, 0, SEEK_SET) == -1) { res = false; break; diff --git a/tool/build/dlopen_test.c b/tool/build/dlopen_tester.c similarity index 100% rename from tool/build/dlopen_test.c rename to tool/build/dlopen_tester.c diff --git a/tool/build/renamestr.c b/tool/build/renamestr.c new file mode 100644 index 000000000..1364bc6c3 --- /dev/null +++ b/tool/build/renamestr.c @@ -0,0 +1,283 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 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/elf/def.h" +#include "libc/elf/elf.h" +#include "libc/elf/scalar.h" +#include "libc/elf/struct/ehdr.h" +#include "libc/elf/struct/phdr.h" +#include "libc/intrin/kprintf.h" +#include "libc/intrin/likely.h" +#include "libc/macros.h" +#include "libc/mem/mem.h" +#include "libc/runtime/runtime.h" +#include "libc/runtime/symbols.internal.h" +#include "libc/stdio/stdio.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/map.h" +#include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/prot.h" +#include "third_party/getopt/getopt.internal.h" + +#define VERSION \ + "renamestr v0.1\n" \ + "https://github.com/jart/cosmopolitan\n" + +#define MANUAL \ + " -f FROM -t TO INPUT \n" \ + "\n" \ + "DESCRIPTION\n" \ + "\n" \ + " in-place string replacement in ELF binary .rodata\n" \ + "\n" \ + " this program may be used to replace strings in the\n" \ + " .rodata sections of ELF binaries, in-place.\n" \ + "\n" \ + "FLAGS\n" \ + "\n" \ + " -h show usage\n" \ + "\n" \ + " -v show version\n" \ + "\n" \ + " -f FROM source string to replace\n" \ + "\n" \ + " -t TO target string replacement. must be shorter\n" \ + " than FROM string for replacement to work\n" \ + "\n" \ + " INPUT ELF binary containing strings to replace\n" \ + "\n" + +static const char *prog; +static const char *exepath; +static Elf64_Shdr *rodata; +static char *rostart; +static char *roend; +static int exefd; + +static wontreturn void Die(const char *thing, const char *reason) { + tinyprint(2, thing, ": ", reason, "\n", NULL); + exit(1); +} + +static wontreturn void DieSys(const char *thing) { + perror(thing); + exit(1); +} + +static wontreturn void ShowUsage(int rc, int fd) { + tinyprint(fd, "USAGE\n\n ", prog, MANUAL, NULL); + exit(rc); +} + +static void Pwrite(const void *data, size_t size, uint64_t offset) { + ssize_t rc; + const char *p, *e; + for (p = data, e = p + size; p < e; p += (size_t)rc, offset += (size_t)rc) { + if ((rc = pwrite(exefd, p, e - p, offset)) == -1) { + DieSys(exepath); + } + } +} + +struct String { + const char *str; + size_t len; +}; + +struct Param { + struct String from; + struct String to; + int count; + char *roloc; +}; + +struct Params { + int n; + struct Param p[4]; +}; + +static struct Params params; + +static void GetOpts(int argc, char *argv[]) { + int opt; + bool partial = false; + params.n = 0; + struct Param *param; + while ((opt = getopt(argc, argv, "hvf:t:")) != -1) { + if (params.n >= ARRAYLEN(params.p)) { + param = NULL; + } else { + param = &(params.p[params.n]); + } + switch (opt) { + case 'f': + if (!param) { + Die(prog, "too many replacements provided"); + } + if (param->from.str) { + Die(prog, "from string already provided"); + } + param->from.str = optarg; + param->from.len = strlen(optarg); + partial = !partial; + break; + case 't': + if (!param) { + Die(prog, "too many replacements provided"); + } + if (param->to.str) { + Die(prog, "to string already provided"); + } + param->to.str = optarg; + param->to.len = strlen(optarg); + partial = !partial; + break; + case 'v': + tinyprint(0, VERSION, NULL); + exit(0); + case 'h': + ShowUsage(0, 1); + default: + ShowUsage(1, 2); + } + if (param->from.str && param->to.str) { + if (param->from.len < param->to.len) { + Die(prog, "to.str longer than from.str, cannot replace"); + } + params.n++; + } + } + if (params.n == 0) { + Die(prog, "no replacements provided"); + } + if (partial) { + Die(prog, "partial replacement provided"); + } + if (optind == argc) { + Die(prog, "missing input argument"); + } + if (optind != argc - 1) { + Die(prog, "too many args"); + } + exepath = argv[optind]; +} + +struct Input { + union { + char *map; + Elf64_Ehdr *elf; + unsigned char *umap; + }; + size_t size; + const char *path; +}; + +static struct Input input; + +static void OpenInput(const char *path) { + int fd; + if ((fd = open(path, O_RDWR)) == -1) + DieSys(path); + if ((input.size = lseek(fd, 0, SEEK_END)) == -1) + DieSys(path); + input.path = path; + input.map = mmap(0, input.size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + if (input.map == MAP_FAILED) + DieSys(path); + if (!IsElf64Binary(input.elf, input.size)) + Die(path, "not an elf64 binary"); + exefd = fd; +} + +static void ReplaceString(struct Param *param) { + size_t len; + char *x = (char *)memchr(param->roloc, 0, roend - param->roloc); + memmove(param->roloc, param->to.str, param->to.len); + if (UNLIKELY(x == NULL)) { + len = roend - param->roloc; + memmove(param->roloc + param->to.len, param->roloc + param->from.len, + len - param->from.len); + } else { + len = x - param->roloc; + memmove(param->roloc + param->to.len, param->roloc + param->from.len, + len + 1 - param->from.len); + } + param->roloc += param->to.len; +} + +int main(int argc, char *argv[]) { +#ifdef MODE_DBG + ShowCrashReports(); +#endif + + prog = argv[0]; + + if (!prog) + prog = "renamestr"; + + GetOpts(argc, argv); + OpenInput(exepath); + rodata = FindElfSectionByName( + input.elf, input.size, + GetElfSectionNameStringTable(input.elf, input.size), ".rodata"); + if (!rodata) + Die(exepath, "doesn't have .rodata"); + + rostart = GetElfSectionAddress(input.elf, input.size, rodata); + if (!rostart) + Die(prog, "could not get to start of .rodata"); + roend = rostart + rodata->sh_size; + +#ifdef MODE_DBG + kprintf("elf file to process: %s\n", exepath); + kprintf("file size is %ld\n", input.size); +#endif + for (int i = 0; i < params.n; ++i) { + struct Param *param = &(params.p[i]); + param->roloc = rostart; + param->count = 0; +#ifdef MODE_DBG + kprintf("need to replace '%s' with '%s'\n", param->from.str, param->to.str); +#endif + } + +#define NEXT_ROLOC(z) \ + memmem((z)->roloc, roend - (z)->roloc, (z)->from.str, (z)->from.len) + for (int i = 0; i < params.n; ++i) { + struct Param *param = &(params.p[i]); + for (param->roloc = NEXT_ROLOC(param); param->roloc != NULL; + param->roloc = NEXT_ROLOC(param)) { + ReplaceString(param); + param->count++; + } + } +#undef NEXT_ROLOC + + Pwrite(input.map, input.size, 0); + if (close(exefd)) { + Die(prog, "unable to close file after writing"); + } + + for (int i = 0; i < params.n; ++i) { + struct Param *param = &(params.p[i]); + printf("'%s' -> '%s': %d replacements\n", param->from.str, param->to.str, + param->count); + } + return 0; +} diff --git a/tool/build/runit.c b/tool/build/runit.c index 5438669e3..1b123f3ea 100644 --- a/tool/build/runit.c +++ b/tool/build/runit.c @@ -384,6 +384,10 @@ int RunOnHost(char *spec) { handshake_latency = timespec_tomicros(timespec_sub(timespec_mono(), start)); if (!err) break; + if (err == MBEDTLS_ERR_NET_CONN_RESET) { + close(g_sock); + continue; + } WARNF("handshake with %s:%d failed -0x%04x (%s)", // g_hostname, g_runitdport, err, GetTlsError(err)); close(g_sock); diff --git a/tool/cosmocc/README.md b/tool/cosmocc/README.md index 9674754dc..84802987e 100644 --- a/tool/cosmocc/README.md +++ b/tool/cosmocc/README.md @@ -1,7 +1,7 @@ # Cosmopolitan Toolchain This toolchain can be used to compile executables that run on Linux / -MacOS / Windows / FreeBSD / OpenBSD / NetBSD for both the x86_64 and +MacOS / Windows / FreeBSD / OpenBSD 7.3 / NetBSD for both the x86_64 and AARCH64 architectures. In addition to letting you create portable binaries, your toolchain is itself comprised of portable binaries, enabling you to have a consistent development environment that lets you @@ -88,6 +88,33 @@ format used by the host system; however it's also possible to explicitly convert APE programs to any architectures / OS combination. For further details on usage, run the `assimilate -h` command. + +## Binary archive format + +The APE format includes another portability superpower: the ability to +distribute application support files WITHIN the compiled executable file. +This is because APE files are also mostly regular zip files! You will +need a copy of a compatible zip tool like the modified version of +Info-ZIP available here: https://cosmo.zip/pub/cosmos/bin/zip. With this +in hand the following command: + +```sh +zip [APE file] [support_file.txt] +``` + +adds support_file.txt to your executable. You can see it listed within +the archive with `unzip -l [APE file]`. + +Cosmo libc includes compatible file handling functions for accessing the +contents of an APE file at the special '/zip' path. So your code is now +able to do the following: + +```c +if (access( "/zip/support_file.txt", F_OK) == 0) { + fprintf(stderr, "/zip/support_file.txt FOUND and can be used as an asset\n"); +} +``` + ## Gotchas If you use zsh and have trouble running APE programs try `sh -c ./prog` @@ -164,15 +191,22 @@ The following supplemental flags are defined by cosmocc: - `-mdbg` may be passed when linking programs. It has the same effect as `export MODE=dbg` in that it will cause an alternative build of the Cosmopolitan Libc runtime to be linked that was built with `-O0 -g`. - Under the normal build mode, `--ftrace` output is oftentimes missing - important pieces of the puzzle due to inlining. This mode makes it - more comprehensible. It's also the only way to make using GDB to - troubleshoot issues inside Cosmo Libc work reliably. Please be warned - that this flag may enable some heavyweight runtime checks. For - example, mmap() will become O(n) rather than O(logn) in an effort to - spot data structure corruption. Lastly, the linked Cosmo runtime was - compiled with `-fsanitize=undefined` (UBSAN) although you still need - to pass that flag too if you want it for your own code. + Under the normal build mode, `--ftrace` output generated by your libc + is oftentimes missing important details due to inlining. If your build + your code with `cosmocc -O0 -mdbg` then `--ftrace` will make much more + sense. It's also the only way to make using GDB to troubleshoot issues + inside Cosmo Libc work reliably. Please be warned, this flag enables + some heavy-hitting runtime checks, such such lock graph validation. + The debug Cosmopolitan runtime is able to detect lock cycles globally + automatically via your normal usage of `pthread_mutex_t` and then + report strongly connected components with C++ symbol demangling. This + runtime will absolutely crash your entire process, if it helps you + spot a bug. For example, debug cosmo is build with UBSAN so even an + undiscovered yet innocent bit shift of a negative number could take + you down. So you wouldn't want to use this in prod very often. Please + note that passing `-mdbg` doesn't imply `-g -O0 -fsanitize=undefined` + which must be passed separately if you want your code to be compiled + with the same stuff as libc. - `-mtiny` may be passed when linking programs. It has the same effect as `export MODE=tiny` in that it will cause an alternative build of @@ -425,7 +459,7 @@ statements instead, so that Cosmopolitan Libc's system constants will work as expected. Our modifications to GNU GCC are published under the ISC license at . The binaries you see here were first published at - which + which is regularly updated. ## Legal diff --git a/tool/cosmocc/bin/cosmocc b/tool/cosmocc/bin/cosmocc index fba76f91d..3019d0621 100755 --- a/tool/cosmocc/bin/cosmocc +++ b/tool/cosmocc/bin/cosmocc @@ -89,14 +89,14 @@ use_gcc() { use_clang() { CLANG=1 - CC_X86_64="$BIN/cosmo-clang" - CC_AARCH64="$BIN/cosmo-clang" - CXX_X86_64="$BIN/cosmo-clang" - CXX_AARCH64="$BIN/cosmo-clang" + CC_X86_64="$BIN/../libexec/clang" + CC_AARCH64="$BIN/../libexec/clang" + CXX_X86_64="$BIN/../libexec/clang" + CXX_AARCH64="$BIN/../libexec/clang" TARGET_X86_64="--target=x86_64" TARGET_AARCH64="--target=aarch64" FPORTCOSMO= - FNO_INLINE_FUNCTIONS_CALLED_ONCE="-fno-inline-functions-called-once" + FNO_INLINE_FUNCTIONS_CALLED_ONCE= } use_gcc diff --git a/tool/cosmocc/bin/cosmoranlib b/tool/cosmocc/bin/cosmoranlib old mode 100644 new mode 100755 diff --git a/tool/cosmocc/package.sh b/tool/cosmocc/package.sh index ae2e6ffbc..9c9ada64a 100755 --- a/tool/cosmocc/package.sh +++ b/tool/cosmocc/package.sh @@ -15,17 +15,49 @@ mode() { esac } +_nproc() { + case $(uname -s) in + Darwin) sysctl -n hw.logicalcpu ;; + *) nproc ;; + esac +} + +TMPDIR=${TMPDIR:-/tmp} OUTDIR=${1:-cosmocc} APELINK=o/$(mode)/tool/build/apelink AMD64=${2:-x86_64} ARM64=${3:-aarch64} -NPROC=$(($(nproc)/2)) +NPROC=$(($(_nproc)/2)) GCCVER=14.1.0 -make -j$NPROC m= \ +if ! MAKE=$(command -v gmake); then + if ! MAKE=$(command -v make); then + echo please install gnu make >&2 + exit 1 + fi +fi + +$MAKE -j$NPROC m= \ $APELINK -make -j$NPROC m=$AMD64 \ +if ! APE=$(command -v ape); then + case $(uname -s) in + Darwin) + case $(mode) in + aarch64) + cc -O -o "$TMPDIR/ape.$$" .cosmocc/current/bin/ape-m1.c || exit + trap 'rm "$TMPDIR/ape.$$"' EXIT + APE=$TMPDIR/ape.$$ + ;; + *) APE=.cosmocc/current/bin/ape-x86_64.macho ;; + esac + ;; + *) APE=.cosmocc/current/bin/ape-$(uname -m).elf ;; + esac +fi +stat $APE + +$MAKE -j$NPROC m=$AMD64 \ o/cosmocc.h.txt \ o/$AMD64/ape/ape.lds \ o/$AMD64/libc/crt/crt.o \ @@ -62,7 +94,7 @@ make -j$NPROC m=$AMD64 \ o/$AMD64/third_party/make/make.dbg \ o/$AMD64/third_party/ctags/ctags.dbg -make -j$NPROC m=$AMD64-tiny \ +$MAKE -j$NPROC m=$AMD64-tiny \ o/cosmocc.h.txt \ o/$AMD64-tiny/ape/ape.lds \ o/$AMD64-tiny/libc/crt/crt.o \ @@ -74,7 +106,7 @@ make -j$NPROC m=$AMD64-tiny \ o/$AMD64-tiny/cosmopolitan.a \ o/$AMD64-tiny/third_party/libcxx/libcxx.a \ -make -j$NPROC m=$AMD64-dbg \ +$MAKE -j$NPROC m=$AMD64-dbg \ o/cosmocc.h.txt \ o/$AMD64-dbg/ape/ape.lds \ o/$AMD64-dbg/libc/crt/crt.o \ @@ -86,7 +118,7 @@ make -j$NPROC m=$AMD64-dbg \ o/$AMD64-dbg/cosmopolitan.a \ o/$AMD64-dbg/third_party/libcxx/libcxx.a \ -make CONFIG_TARGET_ARCH= -j$NPROC m=$AMD64-optlinux \ +$MAKE CONFIG_TARGET_ARCH= -j$NPROC m=$AMD64-optlinux \ o/cosmocc.h.txt \ o/$AMD64-optlinux/ape/ape.lds \ o/$AMD64-optlinux/libc/crt/crt.o \ @@ -98,7 +130,7 @@ make CONFIG_TARGET_ARCH= -j$NPROC m=$AMD64-optlinux \ o/$AMD64-optlinux/cosmopolitan.a \ o/$AMD64-optlinux/third_party/libcxx/libcxx.a \ -make -j$NPROC m=$ARM64 \ +$MAKE -j$NPROC m=$ARM64 \ o/$ARM64/ape/ape.elf \ o/$ARM64/ape/aarch64.lds \ o/$ARM64/libc/crt/crt.o \ @@ -130,21 +162,21 @@ make -j$NPROC m=$ARM64 \ o/$ARM64/third_party/make/make.dbg \ o/$ARM64/third_party/ctags/ctags.dbg -make -j$NPROC m=$ARM64-tiny \ +$MAKE -j$NPROC m=$ARM64-tiny \ o/$ARM64-tiny/ape/ape.elf \ o/$ARM64-tiny/ape/aarch64.lds \ o/$ARM64-tiny/libc/crt/crt.o \ o/$ARM64-tiny/cosmopolitan.a \ o/$ARM64-tiny/third_party/libcxx/libcxx.a \ -make -j$NPROC m=$ARM64-dbg \ +$MAKE -j$NPROC m=$ARM64-dbg \ o/$ARM64-dbg/ape/ape.elf \ o/$ARM64-dbg/ape/aarch64.lds \ o/$ARM64-dbg/libc/crt/crt.o \ o/$ARM64-dbg/cosmopolitan.a \ o/$ARM64-dbg/third_party/libcxx/libcxx.a \ -make -j$NPROC m=$ARM64-optlinux \ +$MAKE -j$NPROC m=$ARM64-optlinux \ o/$ARM64-optlinux/ape/ape.elf \ o/$ARM64-optlinux/ape/aarch64.lds \ o/$ARM64-optlinux/libc/crt/crt.o \ @@ -169,14 +201,36 @@ fetch() { else curl -LO $1 fi + + if command -v sha256sum >/dev/null 2>&1; then + # can use system sha256sum + true + elif command -v shasum >/dev/null 2>&1; then + sha256sum() { + shasum -a 256 "$@" + } + elif command -v "$PWD/o/build/sha256sum" >/dev/null 2>&1; then + # should have been built by download-cosmocc.sh if a system + # sha256sum/shasum does not exist + sha256sum() { + "$PWD/o/build/sha256sum" "$@" + } + else + echo please install sha256sum >&2 + exit 1 + fi + + filename=$(basename $1) + printf '%s\n' "$2 $filename" >$filename.sha256sum + sha256sum -c $filename.sha256sum || exit 1 } OLD=$PWD cd "$OUTDIR/" if [ ! -x bin/x86_64-linux-cosmo-gcc ]; then - fetch https://github.com/ahgamut/superconfigure/releases/download/z0.0.55/aarch64-gcc.zip & - fetch https://github.com/ahgamut/superconfigure/releases/download/z0.0.55/x86_64-gcc.zip & - fetch https://github.com/ahgamut/superconfigure/releases/download/z0.0.55/llvm.zip & + fetch https://github.com/ahgamut/superconfigure/releases/download/z0.0.60/aarch64-gcc.zip 6a07f915ec0296cd33b3142e75c00ed1a7072c75d92c82a0c0b5f5df2cff0dd2 & + fetch https://github.com/ahgamut/superconfigure/releases/download/z0.0.60/x86_64-gcc.zip cbb1659c56a0a4f95a71f59f94693515000d3dd53f79a597acacd53cbad2c7d8 & + fetch https://github.com/ahgamut/superconfigure/releases/download/z0.0.60/llvm.zip d42c2e46204d4332975d2d7464c5df63c898c34f8d9d2b83c168c14705ca8edd & wait unzip aarch64-gcc.zip & unzip x86_64-gcc.zip & @@ -185,7 +239,7 @@ if [ ! -x bin/x86_64-linux-cosmo-gcc ]; then rm -f aarch64-gcc.zip rm -f x86_64-gcc.zip rm -f llvm.zip - mv bin/clang-19 bin/cosmo-clang + mv bin/clang-19 libexec/clang # use `cosmocc -mclang` instead fi rm -f bin/*-cpp rm -f bin/*-gcc-* @@ -272,7 +326,7 @@ cp -f o/$ARM64/ape/ape.elf "$OUTDIR/bin/ape-aarch64.elf" for x in assimilate march-native mktemper fixupobj zipcopy apelink pecheck mkdeps zipobj \ ar chmod cocmd cp echo gzip objbincopy package rm touch mkdir compile sha256sum \ resymbol; do - ape $APELINK \ + $APE $APELINK \ -l o/$AMD64/ape/ape.elf \ -l o/$ARM64/ape/ape.elf \ -M ape/ape-m1.c \ @@ -286,7 +340,7 @@ for x in ar chmod cp echo gzip package rm touch mkdir compile sha256sum; do done for x in make ctags; do - ape $APELINK \ + $APE $APELINK \ -l o/$AMD64/ape/ape.elf \ -l o/$ARM64/ape/ape.elf \ -M ape/ape-m1.c \ diff --git a/tool/hello/BUILD.mk b/tool/hello/BUILD.mk index 2a899b671..aff7a82c0 100644 --- a/tool/hello/BUILD.mk +++ b/tool/hello/BUILD.mk @@ -94,4 +94,6 @@ o/$(MODE)/tool/hello/wait-pe.ape: \ o/$(MODE)/tool/build/elf2pe @$(COMPILE) -AELF2PE o/$(MODE)/tool/build/elf2pe -R 64kb -S 4kb -o $@ $< +o/$(MODE)/tool/hello/life-pe.ape.zip.o: private ZIPOBJ_FLAGS += -B + $(TOOL_HELLO_OBJS): tool/hello/BUILD.mk diff --git a/tool/net/BUILD.mk b/tool/net/BUILD.mk index 52d55f7ec..06a80d5f8 100644 --- a/tool/net/BUILD.mk +++ b/tool/net/BUILD.mk @@ -117,7 +117,8 @@ o/$(MODE)/tool/net/redbean.dbg: \ o/$(MODE)/tool/net/lsqlite3.o: private \ CFLAGS += \ - -DSQLITE_ENABLE_SESSION + -DSQLITE_ENABLE_SESSION \ + -DSQLITE_ENABLE_DESERIALIZE # REDBEAN-DEMO # diff --git a/tool/net/definitions.lua b/tool/net/definitions.lua index 7bc219177..3732416b0 100644 --- a/tool/net/definitions.lua +++ b/tool/net/definitions.lua @@ -5188,11 +5188,14 @@ function unix.fork() end --- unix.execve(prog, {prog, '-hal', '.'}, {'PATH=/bin'}) --- unix.exit(127) --- ---- We automatically suffix `.com` and `.exe` for all platforms when ---- path searching. By default, the current directory is not on the ---- path. If `prog` is an absolute path, then it's returned as-is. If ---- `prog` contains slashes then it's not path searched either and will ---- be returned if it exists. +--- If `prog` is an absolute path, then it's returned as-is. If `prog` +--- contains slashes then it's not path searched either and will be +--- returned if it exists. On Windows, it's recommended that you install +--- programs from cosmos to c:/bin/ without any .exe or .com suffix, so +--- they can be discovered like they would on UNIX. If you want to find +--- a program like notepad on the $PATH using this function, then you +--- need to specify "notepad.exe" so it includes the extension. +--- ---@param prog string ---@return string path ---@overload fun(prog: string): nil, error: unix.Errno diff --git a/tool/net/fetch.inc b/tool/net/fetch.inc index 16fa05a56..8be5775b0 100644 --- a/tool/net/fetch.inc +++ b/tool/net/fetch.inc @@ -378,7 +378,7 @@ static int LuaFetch(lua_State *L) { WARNF("(ftch) HTTP client %s error", "EOF headers"); goto TransportError; } - rc = ParseHttpMessage(&msg, inbuf.p, inbuf.n, inbuf.c); + rc = ParseHttpMessage(&msg, inbuf.p, inbuf.n, SHRT_MAX); if (rc == -1) { WARNF("(ftch) HTTP client %s error", "ParseHttpMessage"); goto TransportError; diff --git a/tool/net/help.txt b/tool/net/help.txt index a217b7f07..ab017578b 100644 --- a/tool/net/help.txt +++ b/tool/net/help.txt @@ -569,7 +569,7 @@ HOOKS *). See functions like Route which asks redbean to do its default thing from the handler. - OnError(status:int, message:string) + OnError(status:int, message:string, details:string) If this function is defined and if any errors occurs in OnHttpRequest() then this method will be called instead of displaying the default error page. Useful if you need to display the error page @@ -2877,11 +2877,13 @@ UNIX MODULE unix.execve(prog, {prog, '-hal', '.'}, {'PATH=/bin'}) unix.exit(127) - We automatically suffix `.com` and `.exe` for all platforms when - path searching. By default, the current directory is not on the - path. If `prog` is an absolute path, then it's returned as-is. If - `prog` contains slashes then it's not path searched either and will - be returned if it exists. + If `prog` is an absolute path, then it's returned as-is. If `prog` + contains slashes then it's not path searched either and will be + returned if it exists. On Windows, it's recommended that you install + programs from cosmos to c:/bin/ without any .exe or .com suffix, so + they can be discovered like they would on UNIX. If you want to find + a program like notepad on the $PATH using this function, then you + need to specify "notepad.exe" so it includes the extension. unix.execve(prog:str[, args:List<*>, env:List<*>]) └─→ nil, unix.Errno @@ -4862,9 +4864,9 @@ UNIX MODULE end It's possible to accomplish the same thing as unix.mapshared() - using files and unix.fcntl() advisory locks. However this goes - significantly faster. For example, that's what SQLite does and - we recommend using SQLite for IPC in redbean. But, if your app + using files and unix.fcntl() advisory locks. For example, that's + what SQLite does and we recommend using SQLite for IPC in redbean. + However, unix.mapshared is significantly faster and if your app has thousands of forked processes fighting for a file lock you might need something lower level than file locks, to implement things like throttling. Shared memory is a good way to do that diff --git a/tool/net/redbean.c b/tool/net/redbean.c index eeb2e3116..93816d1aa 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -181,12 +181,8 @@ __static_yoink("blink_xnu_aarch64"); // is apple silicon #define HeaderLength(H) (cpm.msg.headers[H].b - cpm.msg.headers[H].a) #define HeaderEqualCase(H, S) \ SlicesEqualCase(S, strlen(S), HeaderData(H), HeaderLength(H)) -#define LockInc(P) \ - atomic_fetch_add_explicit((_Atomic(typeof(*(P))) *)(P), +1, \ - memory_order_relaxed) -#define LockDec(P) \ - atomic_fetch_add_explicit((_Atomic(typeof(*(P))) *)(P), -1, \ - memory_order_relaxed) +#define LockInc(P) atomic_fetch_add_explicit(P, +1, memory_order_relaxed) +#define LockDec(P) atomic_fetch_add_explicit(P, -1, memory_order_relaxed) #define TRACE_BEGIN \ do { \ @@ -377,19 +373,21 @@ struct Blackhole { } blackhole; static struct Shared { - int workers; - struct timespec nowish; - struct timespec lastreindex; + _Atomic(int) workers; struct timespec lastmeltdown; + struct timespec nowish; char currentdate[32]; struct rusage server; struct rusage children; struct Counters { -#define C(x) long x; +#define C(x) _Atomic(long) x; #include "tool/net/counters.inc" #undef C } c; - pthread_spinlock_t montermlock; + pthread_mutex_t datetime_mu; + pthread_mutex_t server_mu; + pthread_mutex_t children_mu; + pthread_mutex_t lastmeltdown_mu; } *shared; static const char kCounterNames[] = @@ -1350,8 +1348,8 @@ static void CallSimpleHookIfDefined(const char *s) { } static void ReportWorkerExit(int pid, int ws) { - int workers; - workers = atomic_fetch_sub(&shared->workers, 1) - 1; + int workers = + atomic_fetch_sub_explicit(&shared->workers, 1, memory_order_release); if (WIFEXITED(ws)) { if (WEXITSTATUS(ws)) { LockInc(&shared->c.failedchildren); @@ -1383,7 +1381,9 @@ static void ReportWorkerResources(int pid, struct rusage *ru) { static void HandleWorkerExit(int pid, int ws, struct rusage *ru) { LockInc(&shared->c.connectionshandled); + unassert(!pthread_mutex_lock(&shared->children_mu)); rusage_add(&shared->children, ru); + unassert(!pthread_mutex_unlock(&shared->children_mu)); ReportWorkerExit(pid, ws); ReportWorkerResources(pid, ru); if (hasonprocessdestroy) { @@ -2129,9 +2129,11 @@ static void UpdateCurrentDate(struct timespec now) { int64_t t; struct tm tm; t = now.tv_sec; - shared->nowish = now; gmtime_r(&t, &tm); + unassert(!pthread_mutex_lock(&shared->datetime_mu)); + shared->nowish = now; FormatHttpDateTime(shared->currentdate, &tm); + unassert(!pthread_mutex_unlock(&shared->datetime_mu)); } static int64_t GetGmtOffset(int64_t t) { @@ -2276,7 +2278,7 @@ static struct Asset *GetAssetZip(const char *path, size_t pathlen) { hash = Hash(path, pathlen); for (step = 0;; ++step) { i = (hash + ((step * (step + 1)) >> 1)) & (assets.n - 1); - if (!assets.p[i].hash) + if (i >= assets.n || !assets.p || !assets.p[i].hash) return NULL; if (hash == assets.p[i].hash && pathlen == ZIP_CFILE_NAMESIZE(zmap + assets.p[i].cf) && @@ -2364,7 +2366,10 @@ static char *AppendCache(char *p, int64_t seconds, char *directive) { p = stpcpy(p, directive); } p = AppendCrlf(p); - return AppendExpires(p, shared->nowish.tv_sec + seconds); + unassert(!pthread_mutex_lock(&shared->datetime_mu)); + long nowish_sec = shared->nowish.tv_sec; + unassert(!pthread_mutex_unlock(&shared->datetime_mu)); + return AppendExpires(p, nowish_sec + seconds); } static inline char *AppendContentLength(char *p, size_t n) { @@ -2620,7 +2625,8 @@ static char *ServeErrorImpl(unsigned code, const char *reason, lua_getglobal(L, "OnError"); lua_pushinteger(L, code); lua_pushstring(L, reason); - if (LuaCallWithTrace(L, 2, 0, NULL) == LUA_OK) { + lua_pushstring(L, details); + if (LuaCallWithTrace(L, 3, 0, NULL) == LUA_OK) { return CommitOutput(GetLuaResponse()); } else { return ServeErrorImplDefault(code, reason, details); @@ -3102,9 +3108,12 @@ td { padding-right: 3em; }\r\n\ \r\n\ /statusz\r\n\ "); - if (shared->c.connectionshandled) { + if (atomic_load_explicit(&shared->c.connectionshandled, + memory_order_acquire)) { appends(&cpm.outbuf, "says your redbean
\r\n"); + unassert(!pthread_mutex_lock(&shared->children_mu)); AppendResourceReport(&cpm.outbuf, &shared->children, "
\r\n"); + unassert(!pthread_mutex_unlock(&shared->children_mu)); } appends(&cpm.outbuf, "\r\n"); and = ""; @@ -3126,12 +3135,12 @@ td { padding-right: 3em; }\r\n\ } appendf(&cpm.outbuf, "%s%,ld second%s of operation
\r\n", and, y.rem, y.rem == 1 ? "" : "s"); - x = shared->c.messageshandled; + x = atomic_load_explicit(&shared->c.messageshandled, memory_order_relaxed); appendf(&cpm.outbuf, "%,ld message%s handled
\r\n", x, x == 1 ? "" : "s"); - x = shared->c.connectionshandled; + x = atomic_load_explicit(&shared->c.connectionshandled, memory_order_relaxed); appendf(&cpm.outbuf, "%,ld connection%s handled
\r\n", x, x == 1 ? "" : "s"); - x = shared->workers; + x = atomic_load_explicit(&shared->workers, memory_order_relaxed); appendf(&cpm.outbuf, "%,ld connection%s active
\r\n", x, x == 1 ? "" : "s"); appends(&cpm.outbuf, "\r\n"); @@ -3183,11 +3192,11 @@ static void AppendRusage(const char *a, struct rusage *ru) { } static void ServeCounters(void) { - const long *c; + const _Atomic(long) *c; const char *s; - for (c = (const long *)&shared->c, s = kCounterNames; *s; + for (c = (const _Atomic(long) *)&shared->c, s = kCounterNames; *s; ++c, s += strlen(s) + 1) { - AppendLong1(s, *c); + AppendLong1(s, atomic_load_explicit(c, memory_order_relaxed)); } } @@ -3200,12 +3209,17 @@ static char *ServeStatusz(void) { AppendLong1("pid", getpid()); AppendLong1("ppid", getppid()); AppendLong1("now", timespec_real().tv_sec); + unassert(!pthread_mutex_lock(&shared->datetime_mu)); AppendLong1("nowish", shared->nowish.tv_sec); + unassert(!pthread_mutex_unlock(&shared->datetime_mu)); AppendLong1("gmtoff", gmtoff); AppendLong1("CLK_TCK", CLK_TCK); AppendLong1("startserver", startserver.tv_sec); + unassert(!pthread_mutex_lock(&shared->lastmeltdown_mu)); AppendLong1("lastmeltdown", shared->lastmeltdown.tv_sec); - AppendLong1("workers", shared->workers); + unassert(!pthread_mutex_unlock(&shared->lastmeltdown_mu)); + AppendLong1("workers", + atomic_load_explicit(&shared->workers, memory_order_relaxed)); AppendLong1("assets.n", assets.n); #ifndef STATIC lua_State *L = GL; @@ -3213,8 +3227,12 @@ static char *ServeStatusz(void) { lua_gc(L, LUA_GCCOUNT) * 1024 + lua_gc(L, LUA_GCCOUNTB)); #endif ServeCounters(); + unassert(!pthread_mutex_lock(&shared->server_mu)); AppendRusage("server", &shared->server); + unassert(!pthread_mutex_unlock(&shared->server_mu)); + unassert(!pthread_mutex_lock(&shared->children_mu)); AppendRusage("children", &shared->children); + unassert(!pthread_mutex_unlock(&shared->children_mu)); p = SetStatus(200, "OK"); p = AppendContentType(p, "text/plain"); if (cpm.msg.version >= 11) { @@ -3979,7 +3997,9 @@ static int LuaNilTlsError(lua_State *L, const char *s, int r) { #include "tool/net/fetch.inc" static int LuaGetDate(lua_State *L) { + unassert(!pthread_mutex_lock(&shared->datetime_mu)); lua_pushinteger(L, shared->nowish.tv_sec); + unassert(!pthread_mutex_unlock(&shared->datetime_mu)); return 1; } @@ -5033,7 +5053,7 @@ static int LuaProgramTokenBucket(lua_State *L) { npassert(pid != -1); if (!pid) Replenisher(); - ++shared->workers; + atomic_fetch_add_explicit(&shared->workers, 1, memory_order_acquire); return 0; } @@ -5678,7 +5698,8 @@ static void LogClose(const char *reason) { if (amtread || meltdown || killed) { LockInc(&shared->c.fumbles); INFOF("(stat) %s %s with %,ld unprocessed and %,d handled (%,d workers)", - DescribeClient(), reason, amtread, messageshandled, shared->workers); + DescribeClient(), reason, amtread, messageshandled, + atomic_load_explicit(&shared->workers, memory_order_relaxed)); } else { DEBUGF("(stat) %s %s with %,d messages handled", DescribeClient(), reason, messageshandled); @@ -5736,14 +5757,18 @@ Content-Length: 22\r\n\ } static void EnterMeltdownMode(void) { + unassert(!pthread_mutex_lock(&shared->lastmeltdown_mu)); if (timespec_cmp(timespec_sub(timespec_real(), shared->lastmeltdown), (struct timespec){1}) < 0) { + unassert(!pthread_mutex_unlock(&shared->lastmeltdown_mu)); return; } - WARNF("(srvr) server is melting down (%,d workers)", shared->workers); - LOGIFNEG1(kill(0, SIGUSR2)); shared->lastmeltdown = timespec_real(); - ++shared->c.meltdowns; + pthread_mutex_unlock(&shared->lastmeltdown_mu); + WARNF("(srvr) server is melting down (%,d workers)", + atomic_load_explicit(&shared->workers, memory_order_relaxed)); + LOGIFNEG1(kill(0, SIGUSR2)); + LockInc(&shared->c.meltdowns); } static char *HandlePayloadDisconnect(void) { @@ -5860,7 +5885,9 @@ static void HandleHeartbeat(void) { size_t i; UpdateCurrentDate(timespec_real()); Reindex(); + unassert(!pthread_mutex_lock(&shared->server_mu)); getrusage(RUSAGE_SELF, &shared->server); + unassert(!pthread_mutex_unlock(&shared->server_mu)); #ifndef STATIC CallSimpleHookIfDefined("OnServerHeartbeat"); CollectGarbage(); @@ -6480,7 +6507,9 @@ static bool HandleMessageActual(void) { DEBUGF("(clnt) could not synchronize message stream"); } if (cpm.msg.version >= 10) { + unassert(!pthread_mutex_lock(&shared->datetime_mu)); p = AppendCrlf(stpcpy(stpcpy(p, "Date: "), shared->currentdate)); + unassert(!pthread_mutex_unlock(&shared->datetime_mu)); if (!cpm.branded) p = stpcpy(p, serverheader); if (extrahdrs) @@ -6750,7 +6779,9 @@ static int HandleConnection(size_t i) { DEBUGF("(token) can't acquire accept() token for client"); } startconnection = timespec_real(); - if (UNLIKELY(maxworkers) && shared->workers >= maxworkers) { + if (UNLIKELY(maxworkers) && + atomic_load_explicit(&shared->workers, memory_order_relaxed) >= + maxworkers) { EnterMeltdownMode(); SendServiceUnavailable(); close(client); @@ -6768,6 +6799,8 @@ static int HandleConnection(size_t i) { } else { switch ((pid = fork())) { case 0: + lua_repl_wock(); + lua_repl_lock(); meltdown = false; __isworker = true; connectionclose = false; @@ -7345,6 +7378,14 @@ void RedBean(int argc, char *argv[]) { (shared = mmap(NULL, ROUNDUP(sizeof(struct Shared), getgransize()), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0))); + pthread_mutexattr_t attr; + unassert(!pthread_mutexattr_init(&attr)); + unassert(!pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED)); + unassert(!pthread_mutex_init(&shared->datetime_mu, &attr)); + unassert(!pthread_mutex_init(&shared->server_mu, &attr)); + unassert(!pthread_mutex_init(&shared->children_mu, &attr)); + unassert(!pthread_mutex_init(&shared->lastmeltdown_mu, &attr)); + unassert(!pthread_mutexattr_destroy(&attr)); if (daemonize) { for (int i = 0; i < 256; ++i) { close(i); diff --git a/tool/scripts/flakes b/tool/scripts/flakes new file mode 100755 index 000000000..b054a336a --- /dev/null +++ b/tool/scripts/flakes @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +import os +import sys +import subprocess +import concurrent.futures +from collections import Counter +from typing import List, Dict, Tuple + +NUM_PARALLEL = int(os.cpu_count() * 20) + +def find_test_files(root: str) -> List[str]: + """Find all executable files ending with _test recursively.""" + test_files = [] + if os.path.isdir(root): + for root, _, files in os.walk(root): + for file in files: + if file.endswith('_test'): + file_path = os.path.join(root, file) + if os.access(file_path, os.X_OK): + test_files.append(file_path) + elif root.endswith('_test'): + test_files.append(root) + return test_files + +def run_single_test(test_path: str) -> int: + """Run a single test and return its exit code.""" + try: + result = subprocess.run(["ape", test_path], capture_output=False) + return result.returncode + except Exception as e: + print(f"Error running {test_path}: {e}") + return -1 + +def run_test_multiple_times(test_path: str, iterations: int = NUM_PARALLEL) -> List[int]: + """Run a test multiple times in parallel and collect exit codes.""" + with concurrent.futures.ProcessPoolExecutor() as executor: + futures = [executor.submit(run_single_test, test_path) for _ in range(iterations)] + return [f.result() for f in concurrent.futures.as_completed(futures)] + +def analyze_results(test_path: str, exit_codes: List[int]) -> Tuple[bool, Dict[int, int]]: + """Analyze test results and return if it flaked and error distribution.""" + error_counts = Counter(code for code in exit_codes if code != 0) + return bool(error_counts), dict(error_counts) + +def print_flaky_report(test_path: str, error_distribution: Dict[int, int], total_runs: int): + """Print a report for a flaky test.""" + print(f"{test_path} flaked!") + for exit_code, count in error_distribution.items(): + print(f"* {count}/{total_runs} processes died with exit code {exit_code}") + +def main(directory = "o"): + test_files = find_test_files(directory) + for i, test_path in enumerate(test_files): + print("testing [%d/%d] %s..." % (i, len(test_files), test_path)) + sys.stdout.flush() + exit_codes = run_test_multiple_times(test_path) + is_flaky, error_distribution = analyze_results(test_path, exit_codes) + if is_flaky: + print_flaky_report(test_path, error_distribution, len(exit_codes)) + sys.exit(1) + +if __name__ == "__main__": + main(*sys.argv[1:]) diff --git a/tool/viz/BUILD.mk b/tool/viz/BUILD.mk index 8feaff2b6..5e2ced87d 100644 --- a/tool/viz/BUILD.mk +++ b/tool/viz/BUILD.mk @@ -28,6 +28,7 @@ TOOL_VIZ_DIRECTDEPS = \ LIBC_MEM \ LIBC_NEXGEN32E \ LIBC_NT_COMDLG32 \ + LIBC_NT_NTDLL \ LIBC_NT_GDI32 \ LIBC_NT_KERNEL32 \ LIBC_NT_USER32 \ diff --git a/tool/viz/clock_nanosleep_accuracy.c b/tool/viz/clock_nanosleep_accuracy.c index 0875ab26f..6a8e162d0 100644 --- a/tool/viz/clock_nanosleep_accuracy.c +++ b/tool/viz/clock_nanosleep_accuracy.c @@ -20,8 +20,17 @@ #include #include #include "libc/assert.h" +#include "libc/dce.h" +#include "libc/nt/enum/processcreationflags.h" +#include "libc/nt/enum/status.h" +#include "libc/nt/enum/threadpriority.h" +#include "libc/nt/ntdll.h" +#include "libc/nt/process.h" +#include "libc/nt/runtime.h" +#include "libc/nt/thread.h" +#include "libc/nt/windows.h" -#define MAXIMUM 1e9 +#define MAXIMUM 1e8 #define ITERATIONS 10 const char *MyDescribeClockName(int clock) { @@ -29,6 +38,8 @@ const char *MyDescribeClockName(int clock) { return "CLOCK_REALTIME"; if (clock == CLOCK_MONOTONIC) return "CLOCK_MONOTONIC"; + if (clock == CLOCK_BOOTTIME) + return "CLOCK_BOOTTIME"; if (clock == CLOCK_REALTIME_COARSE) return "CLOCK_REALTIME_COARSE"; if (clock == CLOCK_MONOTONIC_COARSE) @@ -40,7 +51,7 @@ void TestSleepRelative(int clock) { printf("\n"); printf("testing: clock_nanosleep(%s) with relative timeout\n", MyDescribeClockName(clock)); - for (long nanos = 1; nanos < (long)MAXIMUM; nanos *= 2) { + for (long nanos = 1; nanos < (long)MAXIMUM; nanos *= 4) { struct timespec t1, t2, wf; wf = timespec_fromnanos(nanos); if (clock_gettime(clock, &t1)) @@ -57,7 +68,8 @@ void TestSleepRelative(int clock) { int main(int argc, char *argv[]) { TestSleepRelative(CLOCK_REALTIME); - TestSleepRelative(CLOCK_MONOTONIC); TestSleepRelative(CLOCK_REALTIME_COARSE); + TestSleepRelative(CLOCK_MONOTONIC); + TestSleepRelative(CLOCK_BOOTTIME); TestSleepRelative(CLOCK_MONOTONIC_COARSE); } diff --git a/tool/viz/getifaddrs.c b/tool/viz/getifaddrs.c index bd9b22de8..142e8005e 100644 --- a/tool/viz/getifaddrs.c +++ b/tool/viz/getifaddrs.c @@ -33,7 +33,7 @@ eth0 addr: 10.10.10.237 netmask: 255.255.255.0 - broadcast: 255.255.255.0 + broadcast: 10.10.10.255 flags: IFF_UP IFF_BROADCAST IFF_MULTICAST IFF_RUNNING lo @@ -74,13 +74,87 @@ int main(int argc, char *argv[]) { tinyprint(1, "netmask: ", buf, "\n", NULL); } if ((ifa->ifa_flags & IFF_BROADCAST) && - sockaddr2str(ifa->ifa_netmask, buf, sizeof(buf))) { + sockaddr2str(ifa->ifa_broadaddr, buf, sizeof(buf))) { tinyprint(1, "broadcast: ", buf, "\n", NULL); } else if ((ifa->ifa_flags & IFF_POINTOPOINT) && sockaddr2str(ifa->ifa_dstaddr, buf, sizeof(buf))) { tinyprint(1, "dstaddr: ", buf, "\n", NULL); } + if (ifa->ifa_addr->sa_family == AF_INET6) { + int scope = ((int *)ifa->ifa_data)[0]; + int aflags = ((int *)ifa->ifa_data)[1]; + // #define IPV6_ADDR_LOOPBACK 0x0010U + // #define IPV6_ADDR_LINKLOCAL 0x0020U + // #define IPV6_ADDR_SITELOCAL 0x0040U + + // #define IFA_F_TEMPORARY 0x01 + // #define IFA_F_NODAD 0x02 + // #define IFA_F_OPTIMISTIC 0x04 + // #define IFA_F_DADFAILED 0x08 + // #define IFA_F_HOMEADDRESS 0x10 + // #define IFA_F_DEPRECATED 0x20 + // #define IFA_F_TENTATIVE 0x40 + // #define IFA_F_PERMANENT 0x80 + // #define IFA_F_MANAGETEMPADDR 0x100 + // #define IFA_F_NOPREFIXROUTE 0x200 + // #define IFA_F_MCAUTOJOIN 0x400 + // #define IFA_F_STABLE_PRIVACY 0x800 + tinyprint(1, "scope:", NULL); + if (scope == 0x10) { + tinyprint(1, " loopback", NULL); + } + if (scope == 0x20) { + tinyprint(1, " linklocal", NULL); + } + if (scope == 0x40) { + tinyprint(1, " sitelocal", NULL); + } + if (scope == 0x00) { + tinyprint(1, " global", NULL); + } + tinyprint(1, "\n", NULL); + + tinyprint(1, "addr flags:", NULL); + if (aflags & 0x01) { + tinyprint(1, " temporary", NULL); + } + if (aflags & 0x02) { + tinyprint(1, " nodad", NULL); + } + if (aflags & 0x04) { + tinyprint(1, " optimistic", NULL); + } + if (aflags & 0x08) { + tinyprint(1, " dadfailed", NULL); + } + if (aflags & 0x10) { + tinyprint(1, " homeaddress", NULL); + } + if (aflags & 0x20) { + tinyprint(1, " deprecated", NULL); + } + if (aflags & 0x40) { + tinyprint(1, " tentative", NULL); + } + if (aflags & 0x80) { + tinyprint(1, " permanent", NULL); + } + if (aflags & 0x100) { + tinyprint(1, " managetempaddr", NULL); + } + if (aflags & 0x200) { + tinyprint(1, " noprefixroute", NULL); + } + if (aflags & 0x400) { + tinyprint(1, " mcautojoin", NULL); + } + if (aflags & 0x800) { + tinyprint(1, " stable_privacy", NULL); + } + tinyprint(1, "\n", NULL); + } + tinyprint(1, "flags:", NULL); if (ifa->ifa_flags & IFF_UP) { tinyprint(1, " IFF_UP", NULL); diff --git a/tool/zsh/mkofs b/tool/zsh/mkofs index 8018d493a..9e3c8146c 100644 --- a/tool/zsh/mkofs +++ b/tool/zsh/mkofs @@ -17,7 +17,12 @@ cut -d' ' -f2 /proc/mounts | while read -r line; do return 0 fi done +if whence doas >/dev/null; then + doas=doas +else + doas=sudo +fi ( set -x - sudo mount -t tmpfs -o size=10G,noatime,nodiratime /dev/shm "$o" + $doas mount -t tmpfs -o size=10G,noatime,nodiratime /dev/shm "$o" ) # vim:ft=zsh diff --git a/tool/zsh/mmake b/tool/zsh/mmake index 0b5315bae..5efe8cdad 100644 --- a/tool/zsh/mmake +++ b/tool/zsh/mmake @@ -38,8 +38,21 @@ done whence nproc >/dev/null || autoload -Uz nproc j=-j$(nproc) } -local make=${MAKE:-${COSMOCC:-/opt/cosmocc/current}/bin/make} -[[ -x $make ]] || make=${COSMO:-$PWD}/build/bootstrap/make +local make=$( + case $MAKE in + */*) echo $MAKE ;; + ?*) command -v $MAKE ;; + *) echo .cosmocc/current/bin/make ;; + esac +) +if [[ ! -x $make ]]; then + { echo 'please install a suitable make, for example:' + echo + echo 'https://cosmo.zip/pub/cosmos/bin/make' + echo + echo 'then put it on $PATH or point $MAKE to it.' + } >&2; return 1 +fi ( set -x exec $make $j $flags MODE=$mode $targs ) # vim:ft=zsh diff --git a/usr/share/ssl/root/usertrust.pem b/usr/share/ssl/root/usertrust.pem new file mode 100644 index 000000000..789fb50ae --- /dev/null +++ b/usr/share/ssl/root/usertrust.pem @@ -0,0 +1,50 @@ +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW +RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- \ No newline at end of file