From 60cb435cb471f220f46c839e5333051d982f7208 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Sun, 16 Oct 2022 12:05:08 -0700 Subject: [PATCH] Implement pthread_atfork() If threads are being used, then fork() will now acquire and release and runtime locks so that fork() may be safely used from threads. This also makes vfork() thread safe, because pthread mutexes will do nothing when the process is a child of vfork(). More torture tests have been written to confirm this all works like a charm. Additionally: - Invent hexpcpy() api - Rename nsync_malloc_() to kmalloc() - Complete posix named semaphore implementation - Make pthread_create() asynchronous signal safe - Add rm, rmdir, and touch to command interpreter builtins - Invent sigisprecious() and modify sigset functions to use it - Add unit tests for posix_spawn() attributes and fix its bugs One unresolved problem is the reclaiming of *NSYNC waiter memory in the forked child processes, within apps which have threads waiting on locks --- Makefile | 1 + libc/assert.h | 6 +- libc/calls/clock_gettime.c | 2 +- libc/calls/clock_nanosleep.c | 2 +- libc/calls/execve.c | 18 +- libc/calls/gettimeofday.c | 2 +- libc/calls/now.c | 4 +- libc/calls/oldbench.c | 4 +- libc/calls/samplepids.c | 8 +- libc/calls/setpgid.c | 4 +- libc/calls/siglock.c | 8 +- libc/calls/state.internal.h | 4 + libc/calls/struct/sigset.h | 1 + libc/calls/wincrash.c | 32 +- libc/intrin/cxalock.c | 8 + .../sem_name.c => intrin/describebacktrace.c} | 33 +- libc/intrin/describebacktrace.internal.h | 13 + libc/intrin/describeflags.internal.h | 4 +- libc/intrin/describeframe.c | 4 +- libc/intrin/describesigset.c | 26 +- libc/intrin/describestat.c | 8 +- libc/intrin/describestringlist.c | 46 +++ libc/intrin/fds_lock.c | 6 + libc/intrin/g_fds.c | 3 + .../nsync/malloc.c => libc/intrin/kmalloc.c | 77 +++-- libc/intrin/kmalloc.h | 10 + libc/intrin/kprintf.greg.c | 11 +- libc/intrin/mmi_lock.c | 10 + libc/intrin/pthread_atfork.c | 84 +++++ libc/intrin/pthread_key_create.c | 19 +- libc/intrin/pthread_key_delete.c | 4 +- libc/intrin/pthread_key_destruct.c | 9 +- libc/intrin/pthread_mutex_lock.c | 11 + libc/intrin/pthread_mutex_unlock.c | 16 +- libc/intrin/sigaddset.c | 5 +- libc/intrin/sigfillset.c | 3 + .../sem_close.c => intrin/sigisprecious.c} | 16 +- libc/intrin/strace.internal.h | 9 +- libc/intrin/tlsisrequired.c | 1 + libc/intrin/ubsan.c | 4 +- libc/intrin/vforked.c | 20 -- libc/isystem/cpio.h | 4 + libc/log/oncrash.c | 1 - libc/mem/mem.h | 1 - libc/runtime/brk.c | 15 +- libc/runtime/cocmd.c | 60 +++- libc/runtime/enable_threads.c | 7 +- libc/runtime/fork-nt.c | 4 +- libc/runtime/fork.c | 26 +- libc/runtime/memtrack.internal.h | 33 +- libc/runtime/memtrack64.txt | 2 +- libc/runtime/vfork.S | 54 ++-- libc/stdio/dirstream.c | 2 + libc/stdio/fflush_unlocked.c | 37 +++ libc/stdio/posix_spawn.c | 61 ++-- libc/stdio/posix_spawnattr.c | 94 ++++-- libc/str/blake2.c | 4 + libc/{thread/sem_unlink.c => str/hexpcpy.c} | 29 +- libc/str/str.h | 1 + libc/sysv/consts.sh | 26 -- libc/sysv/consts/EX_CANTCREAT.s | 2 - libc/sysv/consts/EX_CONFIG.s | 2 - libc/sysv/consts/EX_NOHOST.s | 2 - libc/sysv/consts/EX_NOINPUT.s | 2 - libc/sysv/consts/EX_NOPERM.s | 2 - libc/sysv/consts/EX_NOUSER.s | 2 - libc/sysv/consts/EX_OK.s | 2 - libc/sysv/consts/EX_OSERR.s | 2 - libc/sysv/consts/EX_OSFILE.s | 2 - libc/sysv/consts/EX_PROTOCOL.s | 2 - libc/sysv/consts/EX_SOFTWARE.s | 2 - libc/sysv/consts/EX_TEMPFAIL.s | 2 - libc/sysv/consts/EX_UNAVAILABLE.s | 2 - libc/sysv/consts/EX_USAGE.s | 2 - libc/sysv/consts/EX__BASE.s | 2 - libc/sysv/consts/EX__MAX.s | 2 - libc/sysv/consts/LINK_MAX.s | 2 + .../sysv/consts/{EX_DATAERR.s => MAX_CANON.s} | 2 +- libc/sysv/consts/{EX_IOERR.s => MAX_INPUT.s} | 2 +- libc/sysv/consts/c.h | 42 +-- libc/sysv/consts/ex.h | 68 ++-- libc/testlib/testrunner.c | 2 +- libc/thread/posixthread.internal.h | 13 +- libc/thread/pthread_atfork.c | 79 +++++ libc/thread/pthread_create.c | 150 +++++---- libc/thread/pthread_zombies.c | 14 +- libc/thread/sem_destroy.c | 19 ++ libc/thread/sem_getvalue.c | 3 + libc/thread/sem_init.c | 16 +- libc/thread/sem_open.c | 306 ++++++++++++++++-- libc/thread/sem_path_np.c | 53 +++ libc/thread/sem_post.c | 14 +- libc/thread/sem_timedwait.c | 12 +- libc/thread/sem_trywait.c | 4 + libc/thread/semaphore.h | 12 +- libc/thread/semaphore.internal.h | 10 - libc/thread/thread.h | 3 +- libc/thread/tls.h | 4 +- libc/time/localtime.c | 23 +- libc/zipos/lock.c | 9 + test/libc/calls/raise_race_test.c | 117 +++++++ test/libc/intrin/describesigset_test.c | 6 + test/libc/intrin/lock_test.c | 1 - test/libc/intrin/pthread_atfork_test.c | 114 +++++++ test/libc/intrin/pthread_mutex_lock_test.c | 10 +- test/libc/stdio/popen_test.c | 12 +- .../{spawn_test.c => posix_spawn_test.c} | 81 ++++- test/libc/stdio/test.mk | 138 ++++---- test/libc/stdio/wut_test.c | 64 ++++ .../libc/str/hexpcpy_test.c | 24 +- test/libc/thread/pthread_create_test.c | 22 +- .../pthread_detach_test.c} | 65 ++-- test/libc/thread/sem_open_test.c | 135 +++++++- test/libc/thread/sem_timedwait_test.c | 115 ++++++- third_party/dlmalloc/dlmalloc.c | 3 + third_party/dlmalloc/init.inc | 12 +- third_party/dlmalloc/locks.inc | 2 +- third_party/dlmalloc/statistics.inc | 1 - third_party/nsync/README.cosmo | 19 ++ third_party/nsync/common.c | 4 +- third_party/nsync/common.internal.h | 4 +- third_party/nsync/futex.c | 16 +- third_party/nsync/malloc.internal.h | 10 - tool/emacs/cosmo-c-constants.el | 1 + 124 files changed, 2169 insertions(+), 718 deletions(-) rename libc/{thread/sem_name.c => intrin/describebacktrace.c} (80%) create mode 100644 libc/intrin/describebacktrace.internal.h create mode 100644 libc/intrin/describestringlist.c rename third_party/nsync/malloc.c => libc/intrin/kmalloc.c (54%) create mode 100644 libc/intrin/kmalloc.h create mode 100644 libc/intrin/pthread_atfork.c rename libc/{thread/sem_close.c => intrin/sigisprecious.c} (86%) delete mode 100644 libc/intrin/vforked.c create mode 100644 libc/isystem/cpio.h rename libc/{thread/sem_unlink.c => str/hexpcpy.c} (76%) delete mode 100644 libc/sysv/consts/EX_CANTCREAT.s delete mode 100644 libc/sysv/consts/EX_CONFIG.s delete mode 100644 libc/sysv/consts/EX_NOHOST.s delete mode 100644 libc/sysv/consts/EX_NOINPUT.s delete mode 100644 libc/sysv/consts/EX_NOPERM.s delete mode 100644 libc/sysv/consts/EX_NOUSER.s delete mode 100644 libc/sysv/consts/EX_OK.s delete mode 100644 libc/sysv/consts/EX_OSERR.s delete mode 100644 libc/sysv/consts/EX_OSFILE.s delete mode 100644 libc/sysv/consts/EX_PROTOCOL.s delete mode 100644 libc/sysv/consts/EX_SOFTWARE.s delete mode 100644 libc/sysv/consts/EX_TEMPFAIL.s delete mode 100644 libc/sysv/consts/EX_UNAVAILABLE.s delete mode 100644 libc/sysv/consts/EX_USAGE.s delete mode 100644 libc/sysv/consts/EX__BASE.s delete mode 100644 libc/sysv/consts/EX__MAX.s create mode 100644 libc/sysv/consts/LINK_MAX.s rename libc/sysv/consts/{EX_DATAERR.s => MAX_CANON.s} (50%) rename libc/sysv/consts/{EX_IOERR.s => MAX_INPUT.s} (50%) create mode 100644 libc/thread/pthread_atfork.c create mode 100644 libc/thread/sem_path_np.c delete mode 100644 libc/thread/semaphore.internal.h create mode 100644 test/libc/calls/raise_race_test.c create mode 100644 test/libc/intrin/pthread_atfork_test.c rename test/libc/stdio/{spawn_test.c => posix_spawn_test.c} (68%) create mode 100644 test/libc/stdio/wut_test.c rename libc/log/malloc_stats.c => test/libc/str/hexpcpy_test.c (73%) rename test/libc/{str/sigset_test.c => thread/pthread_detach_test.c} (61%) create mode 100644 third_party/nsync/README.cosmo delete mode 100644 third_party/nsync/malloc.internal.h diff --git a/Makefile b/Makefile index 357750644..57ed37c1c 100644 --- a/Makefile +++ b/Makefile @@ -92,6 +92,7 @@ o/$(MODE): \ .UNVEIL = \ libc/integral \ libc/disclaimer.inc \ + rwc:/dev/shm \ rx:build/bootstrap \ rx:o/third_party/gcc \ /proc/stat \ diff --git a/libc/assert.h b/libc/assert.h index 502fa86ac..85cd1f2cc 100644 --- a/libc/assert.h +++ b/libc/assert.h @@ -17,7 +17,7 @@ void __assert_fail(const char *, const char *, int) hidden relegated; #endif #ifdef __GNUC__ -#ifndef TINY +#if !defined(TINY) && !defined(MODE_DBG) /** * Specifies expression can't possibly be false. * @@ -36,7 +36,7 @@ void __assert_fail(const char *, const char *, int) hidden relegated; * In `MODE=tiny` these assertions are redefined as undefined behavior. */ #define _npassert(x) \ - ({ \ + __extension__({ \ if (__builtin_expect(!(x), 0)) { \ notpossible; \ } \ @@ -63,7 +63,7 @@ void __assert_fail(const char *, const char *, int) hidden relegated; * that's capable of debugging this macro. */ #define _unassert(x) \ - ({ \ + __extension__({ \ if (__builtin_expect(!(x), 0)) { \ unreachable; \ } \ diff --git a/libc/calls/clock_gettime.c b/libc/calls/clock_gettime.c index 794fecf1b..64134ad11 100644 --- a/libc/calls/clock_gettime.c +++ b/libc/calls/clock_gettime.c @@ -84,7 +84,7 @@ int clock_gettime(int clock, struct timespec *ts) { rc = __clock_gettime(clock, ts); } #if SYSDEBUG - if (!(__get_tls()->tib_flags & TIB_FLAG_TIME_CRITICAL)) { + if (__tls_enabled && !(__get_tls()->tib_flags & TIB_FLAG_TIME_CRITICAL)) { STRACE("clock_gettime(%s, [%s]) → %d% m", DescribeClockName(clock), DescribeTimespec(rc, ts), rc); } diff --git a/libc/calls/clock_nanosleep.c b/libc/calls/clock_nanosleep.c index 89794b27d..8b93f0740 100644 --- a/libc/calls/clock_nanosleep.c +++ b/libc/calls/clock_nanosleep.c @@ -113,7 +113,7 @@ errno_t clock_nanosleep(int clock, int flags, const struct timespec *req, } #if SYSDEBUG - if (!(__get_tls()->tib_flags & TIB_FLAG_TIME_CRITICAL)) { + if (__tls_enabled && !(__get_tls()->tib_flags & TIB_FLAG_TIME_CRITICAL)) { STRACE("clock_nanosleep(%s, %s, %s, [%s]) → %s", DescribeClockName(clock), DescribeSleepFlags(flags), DescribeTimespec(0, req), DescribeTimespec(rc, rem), DescribeErrnoResult(rc)); diff --git a/libc/calls/execve.c b/libc/calls/execve.c index 4a186b218..4af61ccbd 100644 --- a/libc/calls/execve.c +++ b/libc/calls/execve.c @@ -23,6 +23,7 @@ #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" #include "libc/intrin/asan.internal.h" +#include "libc/intrin/describeflags.internal.h" #include "libc/intrin/kprintf.h" #include "libc/intrin/likely.h" #include "libc/intrin/promises.internal.h" @@ -58,21 +59,8 @@ int execve(const char *prog, char *const argv[], char *const envp[]) { !__asan_is_valid_strlist(envp)))) { rc = efault(); } else { -#ifdef SYSDEBUG - if (UNLIKELY(__strace > 0)) { - kprintf(STRACE_PROLOGUE "execve(%#s, {", prog); - for (i = 0; argv[i]; ++i) { - if (i) kprintf(", "); - kprintf("%#s", argv[i]); - } - kprintf("}, {"); - for (i = 0; envp[i]; ++i) { - if (i) kprintf(", "); - kprintf("%#s", envp[i]); - } - kprintf("})\n"); - } -#endif + STRACE("execve(%#s, %s, %s) → ...", prog, DescribeStringList(argv), + DescribeStringList(envp)); if (!IsWindows()) { rc = 0; if (IsLinux() && __execpromises && _weaken(sys_pledge_linux)) { diff --git a/libc/calls/gettimeofday.c b/libc/calls/gettimeofday.c index 6df46f178..2b29751e3 100644 --- a/libc/calls/gettimeofday.c +++ b/libc/calls/gettimeofday.c @@ -63,7 +63,7 @@ int gettimeofday(struct timeval *tv, struct timezone *tz) { rc = __gettimeofday(tv, tz, 0).ax; } #if SYSDEBUG - if (!(__get_tls()->tib_flags & TIB_FLAG_TIME_CRITICAL)) { + if (__tls_enabled && !(__get_tls()->tib_flags & TIB_FLAG_TIME_CRITICAL)) { STRACE("gettimeofday([%s], %p) → %d% m", DescribeTimeval(rc, tv), tz, rc); } #endif diff --git a/libc/calls/now.c b/libc/calls/now.c index 20fb37499..815e152f1 100644 --- a/libc/calls/now.c +++ b/libc/calls/now.c @@ -56,7 +56,7 @@ static long double GetTimeSample(void) { static long double MeasureNanosPerCycle(void) { int i, n; long double avg, samp; - __get_tls()->tib_flags |= TIB_FLAG_TIME_CRITICAL; + if (__tls_enabled) __get_tls()->tib_flags |= TIB_FLAG_TIME_CRITICAL; if (IsWindows()) { n = 10; } else { @@ -66,7 +66,7 @@ static long double MeasureNanosPerCycle(void) { samp = GetTimeSample(); avg += (samp - avg) / i; } - __get_tls()->tib_flags &= ~TIB_FLAG_TIME_CRITICAL; + if (__tls_enabled) __get_tls()->tib_flags &= ~TIB_FLAG_TIME_CRITICAL; STRACE("MeasureNanosPerCycle cpn*1000=%d", (long)(avg * 1000)); return avg; } diff --git a/libc/calls/oldbench.c b/libc/calls/oldbench.c index 7eca8c8f9..68bfdbff9 100644 --- a/libc/calls/oldbench.c +++ b/libc/calls/oldbench.c @@ -53,7 +53,7 @@ static long double GetTimeSample(void) { static long double MeasureNanosPerCycle(void) { int i, n; long double avg, samp; - __get_tls()->tib_flags |= TIB_FLAG_TIME_CRITICAL; + if (__tls_enabled) __get_tls()->tib_flags |= TIB_FLAG_TIME_CRITICAL; if (IsWindows()) { n = 30; } else { @@ -63,7 +63,7 @@ static long double MeasureNanosPerCycle(void) { samp = GetTimeSample(); avg += (samp - avg) / i; } - __get_tls()->tib_flags &= ~TIB_FLAG_TIME_CRITICAL; + if (__tls_enabled) __get_tls()->tib_flags &= ~TIB_FLAG_TIME_CRITICAL; STRACE("MeasureNanosPerCycle cpn*1000=%d", (long)(avg * 1000)); return avg; } diff --git a/libc/calls/samplepids.c b/libc/calls/samplepids.c index b1a6c8016..ae0f144ad 100644 --- a/libc/calls/samplepids.c +++ b/libc/calls/samplepids.c @@ -19,9 +19,9 @@ #include "libc/assert.h" #include "libc/calls/internal.h" #include "libc/dce.h" +#include "libc/stdio/rand.h" #include "libc/thread/thread.h" #include "libc/thread/tls.h" -#include "libc/stdio/lcg.internal.h" /** * Returns handles of windows pids being tracked. @@ -35,12 +35,8 @@ textwindows int __sample_pids(int pids[hasatleast 64], int64_t handles[hasatleast 64], bool exploratory) { - static uint64_t rando = 1; - static pthread_spinlock_t lock; uint32_t i, j, base, count; - if (__threaded) pthread_spin_lock(&lock); - base = KnuthLinearCongruentialGenerator(&rando) >> 32; - pthread_spin_unlock(&lock); + base = _rand64() >> 32; for (count = i = 0; i < g_fds.n; ++i) { j = (base + i) % g_fds.n; if (g_fds.p[j].kind == kFdProcess && (!exploratory || !g_fds.p[j].zombie)) { diff --git a/libc/calls/setpgid.c b/libc/calls/setpgid.c index 3f60fa315..b01395de9 100644 --- a/libc/calls/setpgid.c +++ b/libc/calls/setpgid.c @@ -17,10 +17,10 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" -#include "libc/intrin/strace.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/syscall_support-nt.internal.h" #include "libc/dce.h" +#include "libc/intrin/strace.internal.h" #include "libc/nt/console.h" #include "libc/sysv/errfuns.h" @@ -34,7 +34,7 @@ int setpgid(int pid, int pgid) { rc = sys_setpgid(pid, pgid); } else { me = getpid(); - if (pid == me && pgid == me) { + if ((!pid || pid == me) && (!pgid || pgid == me)) { /* * "When a process is created with CREATE_NEW_PROCESS_GROUP * specified, an implicit call to SetConsoleCtrlHandler(NULL,TRUE) diff --git a/libc/calls/siglock.c b/libc/calls/siglock.c index 5fd32e6ad..b023ecb02 100644 --- a/libc/calls/siglock.c +++ b/libc/calls/siglock.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/state.internal.h" +#include "libc/str/str.h" #include "libc/thread/thread.h" void(__sig_lock)(void) { @@ -27,6 +28,11 @@ void(__sig_unlock)(void) { pthread_mutex_unlock(&__sig_lock_obj); } -__attribute__((__constructor__)) static void init(void) { +void __sig_funlock(void) { + bzero(&__sig_lock_obj, sizeof(__sig_lock_obj)); __sig_lock_obj._type = PTHREAD_MUTEX_RECURSIVE; } + +__attribute__((__constructor__)) static void __sig_init(void) { + pthread_atfork(__sig_lock, __sig_unlock, __sig_funlock); +} diff --git a/libc/calls/state.internal.h b/libc/calls/state.internal.h index 33b2990ad..909b529e5 100644 --- a/libc/calls/state.internal.h +++ b/libc/calls/state.internal.h @@ -16,8 +16,10 @@ hidden extern const struct NtSecurityAttributes kNtIsInheritable; void __fds_lock(void); void __fds_unlock(void); +void __fds_funlock(void); void __sig_lock(void); void __sig_unlock(void); +void __sig_funlock(void); #ifdef _NOPL0 #define __fds_lock() _NOPL0("__threadcalls", __fds_lock) @@ -35,6 +37,8 @@ void __sig_unlock(void); #define __sig_unlock() (__threaded ? __sig_unlock() : 0) #endif +#define __vforked (__tls_enabled && (__get_tls()->tib_flags & TIB_FLAG_VFORKED)) + COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_CALLS_STATE_INTERNAL_H_ */ diff --git a/libc/calls/struct/sigset.h b/libc/calls/struct/sigset.h index 791946141..97d36bda6 100644 --- a/libc/calls/struct/sigset.h +++ b/libc/calls/struct/sigset.h @@ -16,6 +16,7 @@ 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 sigisprecious(int) nosideeffect; int sigprocmask(int, const sigset_t *, sigset_t *); int sigsuspend(const sigset_t *); int sigpending(sigset_t *); diff --git a/libc/calls/wincrash.c b/libc/calls/wincrash.c index b4a467975..cf6e45ca0 100644 --- a/libc/calls/wincrash.c +++ b/libc/calls/wincrash.c @@ -18,21 +18,46 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/sig.internal.h" #include "libc/calls/state.internal.h" -#include "libc/intrin/strace.internal.h" #include "libc/calls/struct/ucontext.internal.h" #include "libc/calls/ucontext.h" +#include "libc/intrin/describebacktrace.internal.h" +#include "libc/intrin/kprintf.h" +#include "libc/intrin/strace.internal.h" #include "libc/nt/enum/exceptionhandleractions.h" #include "libc/nt/enum/signal.h" +#include "libc/nt/runtime.h" #include "libc/nt/struct/ntexceptionpointers.h" #include "libc/str/str.h" #include "libc/sysv/consts/sa.h" #include "libc/sysv/consts/sicode.h" #include "libc/sysv/consts/sig.h" +#include "libc/thread/tls.h" +#include "libc/thread/tls2.h" privileged unsigned __wincrash(struct NtExceptionPointers *ep) { int64_t rip; int sig, code; ucontext_t ctx; + struct CosmoTib *tib; + static bool noreentry; + + if ((tib = __tls_enabled ? __get_tls_privileged() : 0)) { + if (~tib->tib_flags & TIB_FLAG_WINCRASHING) { + tib->tib_flags |= TIB_FLAG_WINCRASHING; + } else { + WincrashPanic: + STRACE("panic: wincrash reentry: rip %x bt %s", ep->ContextRecord->Rip, + DescribeBacktrace((struct StackFrame *)ep->ContextRecord->Rbp)); + ExitProcess(89); + } + } else { + if (!noreentry) { + noreentry = true; + } else { + goto WincrashPanic; + } + } + STRACE("__wincrash"); switch (ep->ExceptionRecord->ExceptionCode) { @@ -115,5 +140,10 @@ privileged unsigned __wincrash(struct NtExceptionPointers *ep) { ep->ContextRecord->Rip++; } + noreentry = false; + if (tib) { + tib->tib_flags &= ~TIB_FLAG_WINCRASHING; + } + return kNtExceptionContinueExecution; } diff --git a/libc/intrin/cxalock.c b/libc/intrin/cxalock.c index 1f7b8d652..c1fde353d 100644 --- a/libc/intrin/cxalock.c +++ b/libc/intrin/cxalock.c @@ -28,3 +28,11 @@ void(__cxa_lock)(void) { void(__cxa_unlock)(void) { pthread_mutex_unlock(&__cxa_lock_obj); } + +void(__cxa_funlock)(void) { + pthread_mutex_init(&__cxa_lock_obj, 0); +} + +__attribute__((__constructor__)) static void __cxa_init(void) { + pthread_atfork(__cxa_lock, __cxa_unlock, __cxa_funlock); +} diff --git a/libc/thread/sem_name.c b/libc/intrin/describebacktrace.c similarity index 80% rename from libc/thread/sem_name.c rename to libc/intrin/describebacktrace.c index 1697c77ec..f3b3ee6b1 100644 --- a/libc/thread/sem_name.c +++ b/libc/intrin/describebacktrace.c @@ -16,22 +16,25 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/describebacktrace.internal.h" #include "libc/intrin/kprintf.h" -#include "libc/runtime/runtime.h" -#include "libc/str/path.h" -#include "libc/str/str.h" -#include "libc/thread/semaphore.internal.h" -#include "libc/thread/thread.h" +#include "libc/nexgen32e/stackframe.h" -const char *__sem_name(const char *name, char path[hasatleast PATH_MAX]) { - const char *res; - if (_isabspath(name)) { - res = name; - } else { - strlcpy(path, kTmpPath, PATH_MAX); - strlcat(path, ".sem-", PATH_MAX); - strlcat(path, name, PATH_MAX); - res = path; +#define N 64 + +#define append(...) o += ksnprintf(buf + o, N - o, __VA_ARGS__) + +const char *(DescribeBacktrace)(char buf[N], struct StackFrame *fr) { + int o = 0; + bool gotsome = false; + while (fr) { + if (gotsome) { + append(" "); + } else { + gotsome = true; + } + append("%x", fr->addr); + fr = fr->next; } - return res; + return buf; } diff --git a/libc/intrin/describebacktrace.internal.h b/libc/intrin/describebacktrace.internal.h new file mode 100644 index 000000000..8682d4719 --- /dev/null +++ b/libc/intrin/describebacktrace.internal.h @@ -0,0 +1,13 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_DESCRIBEBACKTRACE_INTERNAL_H_ +#define COSMOPOLITAN_LIBC_INTRIN_DESCRIBEBACKTRACE_INTERNAL_H_ +#include "libc/mem/alloca.h" +#include "libc/nexgen32e/stackframe.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +const char *DescribeBacktrace(char[64], struct StackFrame *); +#define DescribeBacktrace(x) DescribeBacktrace(alloca(64), x) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_DESCRIBEBACKTRACE_INTERNAL_H_ */ diff --git a/libc/intrin/describeflags.internal.h b/libc/intrin/describeflags.internal.h index 861a7a137..4520272cd 100644 --- a/libc/intrin/describeflags.internal.h +++ b/libc/intrin/describeflags.internal.h @@ -62,6 +62,7 @@ const char *DescribeSocketFamily(char[12], int); const char *DescribeSocketProtocol(char[12], int); const char *DescribeSocketType(char[64], int); const char *DescribeStdioState(char[12], int); +const char *DescribeStringList(char[300], char *const[]); const char *DescribeWhence(char[12], int); const char *DescribeWhichPrio(char[12], int); @@ -69,6 +70,7 @@ const char *DescribeWhichPrio(char[12], int); #define DescribeCapability(x) DescribeCapability(alloca(20), x) #define DescribeClockName(x) DescribeClockName(alloca(32), x) #define DescribeDirfd(x) DescribeDirfd(alloca(12), x) +#define DescribeDnotifyFlags(x) DescribeDnotifyFlags(alloca(80), x) #define DescribeErrno(x) DescribeErrno(alloca(12), x) #define DescribeErrnoResult(x) DescribeErrnoResult(alloca(12), x) #define DescribeFcntlCmd(x) DescribeFcntlCmd(alloca(20), x) @@ -111,9 +113,9 @@ const char *DescribeWhichPrio(char[12], int); #define DescribeSocketProtocol(x) DescribeSocketProtocol(alloca(12), x) #define DescribeSocketType(x) DescribeSocketType(alloca(64), x) #define DescribeStdioState(x) DescribeStdioState(alloca(12), x) +#define DescribeStringList(x) DescribeStringList(alloca(300), x) #define DescribeWhence(x) DescribeWhence(alloca(12), x) #define DescribeWhichPrio(x) DescribeWhichPrio(alloca(12), x) -#define DescribeDnotifyFlags(x) DescribeDnotifyFlags(alloca(80), x) COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/intrin/describeframe.c b/libc/intrin/describeframe.c index c2967194c..c8c7fad8a 100644 --- a/libc/intrin/describeframe.c +++ b/libc/intrin/describeframe.c @@ -54,8 +54,8 @@ static const char *GetFrameName(int x) { return "g_fds"; } else if (IsZiposFrame(x)) { return "zipos"; - } else if (IsNsyncFrame(x)) { - return "nsync"; + } else if (IsKmallocFrame(x)) { + return "kmalloc"; } else if (IsMemtrackFrame(x)) { return "memtrack"; } else if (IsOldStackFrame(x)) { diff --git a/libc/intrin/describesigset.c b/libc/intrin/describesigset.c index 586a7c675..35a0041fd 100644 --- a/libc/intrin/describesigset.c +++ b/libc/intrin/describesigset.c @@ -23,14 +23,15 @@ #include "libc/intrin/kprintf.h" #include "libc/intrin/popcnt.h" #include "libc/str/str.h" +#include "libc/sysv/consts/sig.h" #define N 128 -#define append(...) i += ksnprintf(buf + i, N - i, __VA_ARGS__) +#define append(...) o += ksnprintf(buf + o, N - o, __VA_ARGS__) const char *(DescribeSigset)(char buf[N], int rc, const sigset_t *ss) { - int i, sig; bool gotsome; + int sig, o = 0; sigset_t sigset; if (rc == -1) return "n/a"; @@ -41,17 +42,22 @@ const char *(DescribeSigset)(char buf[N], int rc, const sigset_t *ss) { return buf; } - i = 0; - sigset = *ss; - gotsome = false; - if (popcnt(sigset.__bits[0] & 0xffffffff) > 16) { + if (sigcountset(ss) > 16) { append("~"); - sigset.__bits[0] = ~sigset.__bits[0]; - sigset.__bits[1] = ~sigset.__bits[1]; + sigemptyset(&sigset); + for (sig = 1; sig <= NSIG; ++sig) { + if (!sigismember(ss, sig)) { + sigaddset(&sigset, sig); + } + } + } else { + sigset = *ss; } + append("{"); - for (sig = 1; sig < 32; ++sig) { - if (sigismember(&sigset, sig)) { + gotsome = false; + for (sig = 1; sig <= NSIG; ++sig) { + if (sigismember(&sigset, sig) > 0) { if (gotsome) { append(","); } else { diff --git a/libc/intrin/describestat.c b/libc/intrin/describestat.c index bc67b503a..c844f2247 100644 --- a/libc/intrin/describestat.c +++ b/libc/intrin/describestat.c @@ -24,10 +24,10 @@ #define N 300 -#define append(...) i += ksnprintf(buf + i, N - i, __VA_ARGS__) +#define append(...) o += ksnprintf(buf + o, N - o, __VA_ARGS__) const char *(DescribeStat)(char buf[N], int rc, const struct stat *st) { - int i = 0; + int o = 0; if (rc == -1) return "n/a"; if (!st) return "NULL"; @@ -59,6 +59,10 @@ const char *(DescribeStat)(char buf[N], int rc, const struct stat *st) { append(", .st_%s=%lu", "gid", st->st_gid); } + if (st->st_dev) { + append(", .st_%s=%lu", "dev", st->st_dev); + } + if (st->st_ino) { append(", .st_%s=%lu", "ino", st->st_ino); } diff --git a/libc/intrin/describestringlist.c b/libc/intrin/describestringlist.c new file mode 100644 index 000000000..707889e53 --- /dev/null +++ b/libc/intrin/describestringlist.c @@ -0,0 +1,46 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2022 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/dce.h" +#include "libc/intrin/asan.internal.h" +#include "libc/intrin/describeflags.internal.h" +#include "libc/intrin/kprintf.h" + +#define N 300 + +#define append(...) o += ksnprintf(buf + o, N - o, __VA_ARGS__) + +const char *(DescribeStringList)(char buf[N], char *const list[]) { + int i, o = 0; + + if (!list) return "NULL"; + if (IsAsan() && !__asan_is_valid_strlist(list)) { + ksnprintf(buf, N, "%p", list); + return buf; + } + + append("{"); + i = 0; + do { + if (i++) append(", "); + append("%#s", *list); + } while (*list++); + append("}"); + + return buf; +} diff --git a/libc/intrin/fds_lock.c b/libc/intrin/fds_lock.c index 6097b4a5a..dedbabc99 100644 --- a/libc/intrin/fds_lock.c +++ b/libc/intrin/fds_lock.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/state.internal.h" +#include "libc/str/str.h" #include "libc/thread/thread.h" void(__fds_lock)(void) { @@ -26,3 +27,8 @@ void(__fds_lock)(void) { void(__fds_unlock)(void) { pthread_mutex_unlock(&__fds_lock_obj); } + +void __fds_funlock(void) { + bzero(&__fds_lock_obj, sizeof(__fds_lock_obj)); + __fds_lock_obj._type = PTHREAD_MUTEX_RECURSIVE; +} diff --git a/libc/intrin/g_fds.c b/libc/intrin/g_fds.c index 34c5aee5c..1c0b8f26a 100644 --- a/libc/intrin/g_fds.c +++ b/libc/intrin/g_fds.c @@ -23,6 +23,7 @@ #include "libc/intrin/weaken.h" #include "libc/nt/runtime.h" #include "libc/runtime/memtrack.internal.h" +#include "libc/str/str.h" #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/o.h" #include "libc/thread/thread.h" @@ -43,6 +44,8 @@ static textwindows dontinline void SetupWinStd(struct Fds *fds, int i, int x) { textstartup void InitializeFileDescriptors(void) { struct Fds *fds; __fds_lock_obj._type = PTHREAD_MUTEX_RECURSIVE; + pthread_atfork(_weaken(__fds_lock), _weaken(__fds_unlock), + _weaken(__fds_funlock)); fds = VEIL("r", &g_fds); fds->p = fds->e = (void *)kMemtrackFdsStart; fds->n = 4; diff --git a/third_party/nsync/malloc.c b/libc/intrin/kmalloc.c similarity index 54% rename from third_party/nsync/malloc.c rename to libc/intrin/kmalloc.c index c8ee420d7..0e9190f9d 100644 --- a/third_party/nsync/malloc.c +++ b/libc/intrin/kmalloc.c @@ -1,5 +1,5 @@ -/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│ -│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ │ Copyright 2022 Justine Alexandra Roberts Tunney │ │ │ @@ -16,37 +16,60 @@ │ 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/dce.h" +#include "libc/intrin/asan.internal.h" #include "libc/intrin/atomic.h" #include "libc/intrin/extend.internal.h" +#include "libc/intrin/kprintf.h" #include "libc/macros.internal.h" #include "libc/runtime/memtrack.internal.h" #include "libc/sysv/consts/map.h" -#include "third_party/nsync/common.internal.h" -#include "third_party/nsync/malloc.internal.h" -// clang-format off +#include "libc/thread/thread.h" +#include "libc/thread/tls.h" -static char *nsync_malloc_endptr_; -static size_t nsync_malloc_total_; -static atomic_char nsync_malloc_lock_; +#define KMALLOC_ALIGN __BIGGEST_ALIGNMENT__ -/* nsync_malloc_() is a malloc-like routine used by mutex and condition - variable code to allocate waiter structs. This allows *NSYNC mutexes - to be used by malloc(), by providing another, simpler allocator here. - The intent is that the implicit NULL value here can be overridden by - a client declaration that uses an initializer. */ -void *nsync_malloc_ (size_t size) { - char *start; - size_t offset; - size = ROUNDUP (size, __BIGGEST_ALIGNMENT__); - while (atomic_exchange (&nsync_malloc_lock_, 1)) nsync_yield_ (); - offset = nsync_malloc_total_; - nsync_malloc_total_ += size; - start = (char *) kMemtrackNsyncStart; - if (!nsync_malloc_endptr_) nsync_malloc_endptr_ = start; - nsync_malloc_endptr_ = - _extend (start, nsync_malloc_total_, nsync_malloc_endptr_, - MAP_PRIVATE, kMemtrackNsyncStart + kMemtrackNsyncSize); - atomic_store_explicit (&nsync_malloc_lock_, 0, memory_order_relaxed); - return start + offset; +static struct { + char *endptr; + size_t total; + pthread_spinlock_t lock; +} g_kmalloc; + +static void kmalloc_lock(void) { + if (__threaded) pthread_spin_lock(&g_kmalloc.lock); +} + +static void kmalloc_unlock(void) { + pthread_spin_unlock(&g_kmalloc.lock); +} + +__attribute__((__constructor__)) static void kmalloc_init(void) { + pthread_atfork(kmalloc_lock, kmalloc_unlock, kmalloc_unlock); +} + +/** + * Allocates permanent memory. + * + * The code malloc() depends upon uses this function to allocate memory. + * The returned memory can't be freed, and leak detection is impossible. + * This function panics when memory isn't available. + */ +void *kmalloc(size_t size) { + char *start; + size_t i, n; + n = ROUNDUP(size + (IsAsan() * 8), KMALLOC_ALIGN); + kmalloc_lock(); + i = g_kmalloc.total; + g_kmalloc.total += n; + start = (char *)kMemtrackKmallocStart; + if (!g_kmalloc.endptr) g_kmalloc.endptr = start; + g_kmalloc.endptr = + _extend(start, g_kmalloc.total, g_kmalloc.endptr, MAP_PRIVATE, + kMemtrackKmallocStart + kMemtrackKmallocSize); + kmalloc_unlock(); + _unassert(!((intptr_t)(start + i) & (KMALLOC_ALIGN - 1))); + if (IsAsan()) __asan_poison(start + i + size, n - size, kAsanHeapOverrun); + return start + i; } diff --git a/libc/intrin/kmalloc.h b/libc/intrin/kmalloc.h new file mode 100644 index 000000000..b26bba057 --- /dev/null +++ b/libc/intrin/kmalloc.h @@ -0,0 +1,10 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_KMALLOC_H_ +#define COSMOPOLITAN_LIBC_INTRIN_KMALLOC_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void *kmalloc(size_t) attributeallocsize((1)) mallocesque returnsnonnull; + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_KMALLOC_H_ */ diff --git a/libc/intrin/kprintf.greg.c b/libc/intrin/kprintf.greg.c index 5d108fe72..18c4094c5 100644 --- a/libc/intrin/kprintf.greg.c +++ b/libc/intrin/kprintf.greg.c @@ -206,6 +206,7 @@ privileged static size_t kformat(char *b, size_t n, const char *fmt, const char *abet; signed char type; const char *s, *f; + struct CosmoTib *tib; unsigned long long x; unsigned i, j, m, rem, sign, hash, cols, prec; char c, *p, *e, pdot, zero, flip, dang, base, quot, uppr, ansi, z[128]; @@ -323,12 +324,12 @@ privileged static size_t kformat(char *b, size_t n, const char *fmt, goto FormatUnsigned; case 'P': - if (!__vforked) { - if (!__tls_enabled) { - x = __pid; + 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); } else { - x = atomic_load_explicit(&__get_tls_privileged()->tib_tid, - memory_order_relaxed); + x = sys_gettid(); } if (!__nocolor && p + 7 <= e) { *p++ = '\e'; diff --git a/libc/intrin/mmi_lock.c b/libc/intrin/mmi_lock.c index 0bcbed171..e73040ff9 100644 --- a/libc/intrin/mmi_lock.c +++ b/libc/intrin/mmi_lock.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/runtime/memtrack.internal.h" +#include "libc/str/str.h" #include "libc/thread/thread.h" // this lock currently needs to be (1) recursive and (2) not nsync @@ -30,3 +31,12 @@ void(__mmi_lock)(void) { void(__mmi_unlock)(void) { pthread_mutex_unlock(&__mmi_lock_obj); } + +void(__mmi_funlock)(void) { + bzero(&__mmi_lock_obj, sizeof(__mmi_lock_obj)); + __mmi_lock_obj._type = PTHREAD_MUTEX_RECURSIVE; +} + +__attribute__((__constructor__)) static void init(void) { + pthread_atfork(__mmi_lock, __mmi_unlock, __mmi_funlock); +} diff --git a/libc/intrin/pthread_atfork.c b/libc/intrin/pthread_atfork.c new file mode 100644 index 000000000..dc58b3498 --- /dev/null +++ b/libc/intrin/pthread_atfork.c @@ -0,0 +1,84 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2022 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/weaken.h" +#include "libc/thread/posixthread.internal.h" +#include "libc/thread/thread.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_lock(void) { + * pthread_mutex_lock(&g_lib.lock); + * } + * + * static void lib_unlock(void) { + * pthread_mutex_unlock(&g_lib.lock); + * } + * + * static void lib_funlock(void) { + * pthread_mutex_init(&g_lib.lock, 0); + * } + * + * static void lib_setup(void) { + * lib_funlock(); + * pthread_atfork(lib_lock, lib_unlock, lib_funlock); + * } + * + * static void lib_init(void) { + * pthread_once(&g_lib.once, lib_setup); + * } + * + * void lib(void) { + * lib_init(); + * lib_lock(); + * // do stuff... + * lib_unlock(); + * } + * + * This won't actually aspect fork() until pthread_create() is called, + * since we don't want normal non-threaded programs to have to acquire + * exclusive locks on every resource in the entire app just to fork(). + * + * The vfork() function is *never* aspected. What happens instead is a + * global variable named `__vforked` is set to true in the child which + * causes lock functions to do nothing. So far, it works like a charm. + * + * @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 + */ +int pthread_atfork(atfork_f prepare, atfork_f parent, atfork_f child) { + if (_weaken(_pthread_atfork)) { + return _weaken(_pthread_atfork)(prepare, parent, child); + } else { + return 0; + } +} diff --git a/libc/intrin/pthread_key_create.c b/libc/intrin/pthread_key_create.c index a08317384..6642bf058 100644 --- a/libc/intrin/pthread_key_create.c +++ b/libc/intrin/pthread_key_create.c @@ -28,7 +28,7 @@ */ int pthread_key_create(pthread_key_t *key, pthread_key_dtor dtor) { int i, j, rc = EAGAIN; - pthread_spin_lock(&_pthread_keys_lock); + _pthread_key_lock(); for (i = 0; i < (PTHREAD_KEYS_MAX + 63) / 64; ++i) { if (~_pthread_key_usage[i]) { j = _bsrl(~_pthread_key_usage[i]); @@ -39,14 +39,29 @@ int pthread_key_create(pthread_key_t *key, pthread_key_dtor dtor) { break; } } - pthread_spin_unlock(&_pthread_keys_lock); + _pthread_key_unlock(); return rc; } +void _pthread_key_lock(void) { + pthread_spin_lock(&_pthread_keys_lock); +} + +void _pthread_key_unlock(void) { + pthread_spin_unlock(&_pthread_keys_lock); +} + +static void _pthread_key_funlock(void) { + pthread_spin_init(&_pthread_keys_lock, 0); +} + static textexit void _pthread_key_atexit(void) { _pthread_key_destruct(0); } __attribute__((__constructor__)) static textstartup void _pthread_key_init() { atexit(_pthread_key_atexit); + pthread_atfork(_pthread_key_lock, // + _pthread_key_unlock, // + _pthread_key_funlock); } diff --git a/libc/intrin/pthread_key_delete.c b/libc/intrin/pthread_key_delete.c index b78120c21..5ba9490a7 100644 --- a/libc/intrin/pthread_key_delete.c +++ b/libc/intrin/pthread_key_delete.c @@ -25,7 +25,7 @@ */ int pthread_key_delete(pthread_key_t key) { int rc; - pthread_spin_lock(&_pthread_keys_lock); + _pthread_key_lock(); if (key < PTHREAD_KEYS_MAX) { _pthread_key_usage[key / 64] &= ~(1ul << (key % 64)); _pthread_key_dtor[key] = 0; @@ -33,6 +33,6 @@ int pthread_key_delete(pthread_key_t key) { } else { rc = EINVAL; } - pthread_spin_unlock(&_pthread_keys_lock); + _pthread_key_unlock(); return rc; } diff --git a/libc/intrin/pthread_key_destruct.c b/libc/intrin/pthread_key_destruct.c index 6444f17a8..52309b279 100644 --- a/libc/intrin/pthread_key_destruct.c +++ b/libc/intrin/pthread_key_destruct.c @@ -21,13 +21,14 @@ #include "libc/thread/thread.h" #include "libc/thread/tls.h" +// TODO(jart): why does this even need a lock? void _pthread_key_destruct(void *key[PTHREAD_KEYS_MAX]) { int i, j; uint64_t x; void *value; pthread_key_dtor dtor; if (!__tls_enabled) return; - pthread_spin_lock(&_pthread_keys_lock); + _pthread_key_lock(); if (!key) key = __get_tls()->tib_keys; StartOver: for (i = 0; i < (PTHREAD_KEYS_MAX + 63) / 64; ++i) { @@ -36,13 +37,13 @@ StartOver: j = _bsrl(x); if ((value = key[i * 64 + j]) && (dtor = _pthread_key_dtor[i * 64 + j])) { key[i * 64 + j] = 0; - pthread_spin_unlock(&_pthread_keys_lock); + _pthread_key_unlock(); dtor(value); - pthread_spin_lock(&_pthread_keys_lock); + _pthread_key_lock(); goto StartOver; } x &= ~(1ul << j); } } - pthread_spin_unlock(&_pthread_keys_lock); + _pthread_key_unlock(); } diff --git a/libc/intrin/pthread_mutex_lock.c b/libc/intrin/pthread_mutex_lock.c index f32085889..23c4ee566 100644 --- a/libc/intrin/pthread_mutex_lock.c +++ b/libc/intrin/pthread_mutex_lock.c @@ -17,9 +17,12 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/state.internal.h" #include "libc/errno.h" #include "libc/intrin/atomic.h" +#include "libc/intrin/strace.internal.h" #include "libc/intrin/weaken.h" +#include "libc/runtime/internal.h" #include "libc/thread/thread.h" #include "libc/thread/tls.h" #include "third_party/nsync/mu.h" @@ -55,12 +58,19 @@ * pthread_mutex_unlock(&lock); * pthread_mutex_destroy(&lock); * + * This function does nothing in vfork() children. + * * @return 0 on success, or error number on failure * @see pthread_spin_lock() + * @vforksafe */ int pthread_mutex_lock(pthread_mutex_t *mutex) { int c, d, t; + if (__vforked) return 0; + + LOCKTRACE("pthread_mutex_lock(%t)", mutex); + if (__tls_enabled && // mutex->_type == PTHREAD_MUTEX_NORMAL && // mutex->_pshared == PTHREAD_PROCESS_PRIVATE && // @@ -96,6 +106,7 @@ int pthread_mutex_lock(pthread_mutex_t *mutex) { mutex->_depth = 0; mutex->_owner = t; + mutex->_pid = __pid; return 0; } diff --git a/libc/intrin/pthread_mutex_unlock.c b/libc/intrin/pthread_mutex_unlock.c index b0ba6e4e6..bf8d396b1 100644 --- a/libc/intrin/pthread_mutex_unlock.c +++ b/libc/intrin/pthread_mutex_unlock.c @@ -17,9 +17,12 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/state.internal.h" #include "libc/errno.h" #include "libc/intrin/atomic.h" +#include "libc/intrin/strace.internal.h" #include "libc/intrin/weaken.h" +#include "libc/runtime/internal.h" #include "libc/thread/thread.h" #include "libc/thread/tls.h" #include "third_party/nsync/mu.h" @@ -27,12 +30,19 @@ /** * 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 */ int pthread_mutex_unlock(pthread_mutex_t *mutex) { int c, t; + if (__vforked) return 0; + + LOCKTRACE("pthread_mutex_unlock(%t)", mutex); + if (__tls_enabled && // mutex->_type == PTHREAD_MUTEX_NORMAL && // mutex->_pshared == PTHREAD_PROCESS_PRIVATE && // @@ -47,7 +57,11 @@ int pthread_mutex_unlock(pthread_mutex_t *mutex) { } t = gettid(); - if (mutex->_owner != t) { + + // we allow unlocking an initialized lock that wasn't locked, but we + // don't allow unlocking a lock held by another thread, on unlocking + // recursive locks from a forked child, since it should be re-init'd + if (mutex->_owner && (mutex->_owner != t || mutex->_pid != __pid)) { return EPERM; } diff --git a/libc/intrin/sigaddset.c b/libc/intrin/sigaddset.c index 61e6bc651..a513ad536 100644 --- a/libc/intrin/sigaddset.c +++ b/libc/intrin/sigaddset.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/struct/sigset.h" +#include "libc/sysv/consts/sig.h" #include "libc/sysv/errfuns.h" /** @@ -29,7 +30,9 @@ int sigaddset(sigset_t *set, int sig) { _Static_assert(sizeof(set->__bits[0]) * CHAR_BIT == 64, ""); if (1 <= sig && sig <= NSIG) { - set->__bits[(sig - 1) >> 6] |= 1ull << ((sig - 1) & 63); + if (!sigisprecious(sig)) { + set->__bits[(sig - 1) >> 6] |= 1ull << ((sig - 1) & 63); + } return 0; } else { return einval(); diff --git a/libc/intrin/sigfillset.c b/libc/intrin/sigfillset.c index 7870d1bd7..596c6fb19 100644 --- a/libc/intrin/sigfillset.c +++ b/libc/intrin/sigfillset.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/struct/sigset.h" #include "libc/str/str.h" +#include "libc/sysv/consts/sig.h" /** * Adds all signals to set. @@ -28,5 +29,7 @@ */ int sigfillset(sigset_t *set) { memset(set->__bits, -1, sizeof(set->__bits)); + sigdelset(set, SIGKILL); + sigdelset(set, SIGSTOP); return 0; } diff --git a/libc/thread/sem_close.c b/libc/intrin/sigisprecious.c similarity index 86% rename from libc/thread/sem_close.c rename to libc/intrin/sigisprecious.c index dc577ddb1..37bfcb434 100644 --- a/libc/thread/sem_close.c +++ b/libc/intrin/sigisprecious.c @@ -16,17 +16,13 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/assert.h" -#include "libc/runtime/runtime.h" -#include "libc/thread/semaphore.h" +#include "libc/calls/struct/sigset.h" +#include "libc/sysv/consts/sig.h" /** - * Closes named semaphore. - * - * @param sem was created with sem_open() - * @return 0 on success, or -1 w/ errno + * Returns true if you're not authorized to block this signal. */ -int sem_close(sem_t *sem) { - _npassert(!munmap(sem, FRAMESIZE)); - return 0; +int sigisprecious(int sig) { + return sig == SIGKILL || // + sig == SIGSTOP; } diff --git a/libc/intrin/strace.internal.h b/libc/intrin/strace.internal.h index 75041c2a0..40e411b91 100644 --- a/libc/intrin/strace.internal.h +++ b/libc/intrin/strace.internal.h @@ -7,7 +7,8 @@ #define _POLLTRACE 0 /* not configurable w/ flag yet */ #define _DATATRACE 1 /* not configurable w/ flag yet */ #define _STDIOTRACE 0 /* not configurable w/ flag yet */ -#define _NTTRACE 1 /* not configurable w/ flag yet */ +#define _LOCKTRACE 0 /* not configurable w/ flag yet */ +#define _NTTRACE 0 /* not configurable w/ flag yet */ #define STRACE_PROLOGUE "%rSYS %6P %'18T " @@ -55,6 +56,12 @@ COSMOPOLITAN_C_START_ #define NTTRACE(FMT, ...) (void)0 #endif +#if defined(SYSDEBUG) && _LOCKTRACE +#define LOCKTRACE(FMT, ...) STRACE(FMT, ##__VA_ARGS__) +#else +#define LOCKTRACE(FMT, ...) (void)0 +#endif + void __stracef(const char *, ...); COSMOPOLITAN_C_END_ diff --git a/libc/intrin/tlsisrequired.c b/libc/intrin/tlsisrequired.c index 08fb4d41a..ddb7ca95b 100644 --- a/libc/intrin/tlsisrequired.c +++ b/libc/intrin/tlsisrequired.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/strace.internal.h" #include "libc/thread/tls.h" void __require_tls(void) { diff --git a/libc/intrin/ubsan.c b/libc/intrin/ubsan.c index ab0d6b937..ebaf529ea 100644 --- a/libc/intrin/ubsan.c +++ b/libc/intrin/ubsan.c @@ -228,8 +228,8 @@ static void __ubsan_warning(const struct UbsanSourceLocation *loc, dontdiscard __ubsan_die_f *__ubsan_abort(const struct UbsanSourceLocation *loc, const char *description) { - kprintf("\n%s:%d: %subsan error%s: %s\n", loc->file, loc->line, RED2, RESET, - description); + kprintf("\n%s:%d: %subsan error%s: %s (tid %d)\n", loc->file, loc->line, RED2, + RESET, description, gettid()); return __ubsan_die(); } diff --git a/libc/intrin/vforked.c b/libc/intrin/vforked.c deleted file mode 100644 index a93441dd4..000000000 --- a/libc/intrin/vforked.c +++ /dev/null @@ -1,20 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2021 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ - -int __vforked; diff --git a/libc/isystem/cpio.h b/libc/isystem/cpio.h new file mode 100644 index 000000000..ded259fcc --- /dev/null +++ b/libc/isystem/cpio.h @@ -0,0 +1,4 @@ +#ifndef COSMOPOLITAN_LIBC_ISYSTEM_CPIO_H_ +#define COSMOPOLITAN_LIBC_ISYSTEM_CPIO_H_ +#include "libc/sysv/consts/c.h" +#endif /* COSMOPOLITAN_LIBC_ISYSTEM_CPIO_H_ */ diff --git a/libc/log/oncrash.c b/libc/log/oncrash.c index 79f858165..fe398a8ff 100644 --- a/libc/log/oncrash.c +++ b/libc/log/oncrash.c @@ -284,7 +284,6 @@ relegated void __oncrash(int sig, struct siginfo *si, ucontext_t *ctx) { int gdbpid, err; static int sync; static bool _notpossible; - __tls_enabled = false; STRACE("__oncrash rip %x", ctx->uc_mcontext.rip); --__ftrace; --__strace; diff --git a/libc/mem/mem.h b/libc/mem/mem.h index c353fa112..ef366c04a 100644 --- a/libc/mem/mem.h +++ b/libc/mem/mem.h @@ -51,7 +51,6 @@ struct mallinfo { struct mallinfo mallinfo(void); -void malloc_stats(void); size_t malloc_footprint(void); size_t malloc_max_footprint(void); size_t malloc_footprint_limit(void); diff --git a/libc/runtime/brk.c b/libc/runtime/brk.c index f8a8924a1..b296aa559 100644 --- a/libc/runtime/brk.c +++ b/libc/runtime/brk.c @@ -87,21 +87,26 @@ static unsigned char *brk_unlocked(unsigned char *p) { } } -int brk_lock(void) { +void brk_lock(void) { pthread_mutex_lock(&__brk.m); - return 0; } void brk_unlock(void) { pthread_mutex_unlock(&__brk.m); } +void brk_funlock(void) { + pthread_mutex_init(&__brk.m, 0); +} + +__attribute__((__constructor__)) static void brk_init(void) { + brk_funlock(); + pthread_atfork(brk_lock, brk_unlock, brk_funlock); +} + #ifdef _NOPL0 #define brk_lock() _NOPL0("__threadcalls", brk_lock) #define brk_unlock() _NOPL0("__threadcalls", brk_unlock) -#else -#define brk_lock() (__threaded ? brk_lock() : 0) -#define brk_unlock() (__threaded ? brk_unlock() : 0) #endif /** diff --git a/libc/runtime/cocmd.c b/libc/runtime/cocmd.c index 7a0c0e5a3..6c63a53af 100644 --- a/libc/runtime/cocmd.c +++ b/libc/runtime/cocmd.c @@ -301,7 +301,7 @@ static int Kill(void) { int sig, rc = 0, i = 1; if (i < n && args[i][0] == '-') { sig = GetSignalByName(args[i++] + 1); - if (!sig) return 1; + if (!sig) return -1; // fallback to system kill command } else { sig = SIGTERM; } @@ -340,15 +340,15 @@ static int Usleep(void) { } static int Test(void) { - int w; + int w, m = n; struct stat st; - if (n && READ16LE(args[n - 1]) == READ16LE("]")) --n; - if (n == 4) { + if (m && READ16LE(args[m - 1]) == READ16LE("]")) --m; + if (m == 4) { w = READ32LE(args[2]) & 0x00ffffff; if ((w & 65535) == READ16LE("=")) return !!strcmp(args[1], args[3]); if (w == READ24("==")) return !!strcmp(args[1], args[3]); if (w == READ24("!=")) return !strcmp(args[1], args[3]); - } else if (n == 3) { + } else if (m == 3) { w = READ32LE(args[1]) & 0x00ffffff; if (w == READ24("-n")) return !(strlen(args[2]) > 0); if (w == READ24("-z")) return !(strlen(args[2]) == 0); @@ -357,7 +357,52 @@ static int Test(void) { if (w == READ24("-d")) return !(!stat(args[2], &st) && S_ISDIR(st.st_mode)); if (w == READ24("-h")) return !(!stat(args[2], &st) && S_ISLNK(st.st_mode)); } - return 1; + return -1; // fall back to system test command +} + +static int Rm(void) { + int i; + if (n > 1 && args[1][0] != '-') { + for (i = 1; i < n; ++i) { + if (unlink(args[i])) { + Log("rm: ", args[i], ": ", _strerdoc(errno), 0); + return 1; + } + } + return 0; + } else { + return -1; // fall back to system rm command + } +} + +static int Rmdir(void) { + int i; + if (n > 1 && args[1][0] != '-') { + for (i = 1; i < n; ++i) { + if (rmdir(args[i])) { + Log("rmdir: ", args[i], ": ", _strerdoc(errno), 0); + return 1; + } + } + return 0; + } else { + return -1; // fall back to system rmdir command + } +} + +static int Touch(void) { + int i; + if (n > 1 && args[1][0] != '-') { + for (i = 1; i < n; ++i) { + if (touch(args[i], 0644)) { + Log("touch: ", args[i], ": ", _strerdoc(errno), 0); + return 1; + } + } + return 0; + } else { + return -1; // fall back to system rmdir command + } } static int Fake(int main(int, char **)) { @@ -377,6 +422,7 @@ static int TryBuiltin(void) { if (!n) return 0; if (!strcmp(args[0], "exit")) Exit(); if (!strcmp(args[0], "cd")) return Cd(); + if (!strcmp(args[0], "rm")) return Rm(); if (!strcmp(args[0], "[")) return Test(); if (!strcmp(args[0], "wait")) return Wait(); if (!strcmp(args[0], "echo")) return Echo(); @@ -384,6 +430,8 @@ static int TryBuiltin(void) { if (!strcmp(args[0], "true")) return True(); if (!strcmp(args[0], "test")) return Test(); if (!strcmp(args[0], "kill")) return Kill(); + if (!strcmp(args[0], "touch")) return Touch(); + if (!strcmp(args[0], "rmdir")) return Rmdir(); if (!strcmp(args[0], "mkdir")) return Mkdir(); if (!strcmp(args[0], "false")) return False(); if (!strcmp(args[0], "usleep")) return Usleep(); diff --git a/libc/runtime/enable_threads.c b/libc/runtime/enable_threads.c index 334e64d60..21f0176d6 100644 --- a/libc/runtime/enable_threads.c +++ b/libc/runtime/enable_threads.c @@ -19,6 +19,7 @@ #include "ape/sections.internal.h" #include "libc/assert.h" #include "libc/calls/calls.h" +#include "libc/calls/syscall-sysv.internal.h" #include "libc/intrin/strace.internal.h" #include "libc/runtime/runtime.h" #include "libc/thread/tls.h" @@ -28,7 +29,7 @@ extern int __threadcalls_start[]; #pragma weak __threadcalls_start #pragma weak __threadcalls_end -static privileged dontinline void FixupLocks(void) { +static privileged dontinline void FixupLockNops(void) { __morph_begin(); /* * _NOPL("__threadcalls", func) @@ -59,6 +60,6 @@ static privileged dontinline void FixupLocks(void) { void __enable_threads(void) { if (__threaded) return; STRACE("__enable_threads()"); - FixupLocks(); - __threaded = gettid(); + FixupLockNops(); + __threaded = sys_gettid(); } diff --git a/libc/runtime/fork-nt.c b/libc/runtime/fork-nt.c index 1a2eb6699..0f59649ab 100644 --- a/libc/runtime/fork-nt.c +++ b/libc/runtime/fork-nt.c @@ -273,7 +273,7 @@ textwindows int sys_fork_nt(uint32_t dwCreationFlags) { char *p, forkvar[6 + 21 + 1 + 21 + 1]; struct NtProcessInformation procinfo; if (!setjmp(jb)) { - pid = untrackpid = __reservefd(-1); + pid = untrackpid = __reservefd_unlocked(-1); reader = CreateNamedPipe(CreatePipeName(pipename), kNtPipeAccessInbound | kNtFileFlagOverlapped, kNtPipeTypeMessage | kNtPipeReadmodeMessage, 1, @@ -347,7 +347,7 @@ textwindows int sys_fork_nt(uint32_t dwCreationFlags) { rc = 0; } if (untrackpid != -1) { - __releasefd(untrackpid); + __releasefd_unlocked(untrackpid); } return rc; } diff --git a/libc/runtime/fork.c b/libc/runtime/fork.c index 676bd7de4..4c5fec362 100644 --- a/libc/runtime/fork.c +++ b/libc/runtime/fork.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/sigset.h" #include "libc/calls/struct/sigset.internal.h" @@ -33,14 +34,15 @@ int _fork(uint32_t dwCreationFlags) { axdx_t ad; + bool threaded; sigset_t old, all; - int ax, dx, parent, parent_tid = 0; - if (_weaken(_pthread_atfork)) { - parent_tid = gettid(); + int ax, dx, parent; + sigfillset(&all); + _unassert(!sigprocmask(SIG_BLOCK, &all, &old)); + if (__threaded && _weaken(_pthread_onfork_prepare)) { + _weaken(_pthread_onfork_prepare)(); } if (!IsWindows()) { - sigfillset(&all); - sys_sigprocmask(SIG_BLOCK, &all, &old); ad = sys_fork(); ax = ad.ax; dx = ad.dx; @@ -50,7 +52,11 @@ int _fork(uint32_t dwCreationFlags) { ax &= dx - 1; } } else { + threaded = __threaded; ax = sys_fork_nt(dwCreationFlags); + if (threaded && !__threaded && _weaken(__enable_threads)) { + _weaken(__enable_threads)(); + } } if (!ax) { if (!IsWindows()) { @@ -65,15 +71,17 @@ int _fork(uint32_t dwCreationFlags) { IsLinux() ? dx : sys_gettid(), memory_order_relaxed); } - if (_weaken(_pthread_atfork)) { - _weaken(_pthread_atfork)(parent_tid); + if (__threaded && _weaken(_pthread_onfork_child)) { + _weaken(_pthread_onfork_child)(); } - if (!IsWindows()) sys_sigprocmask(SIG_SETMASK, &old, 0); STRACE("fork() → 0 (child of %d)", parent); } else { - if (!IsWindows()) sys_sigprocmask(SIG_SETMASK, &old, 0); + if (__threaded && _weaken(_pthread_onfork_parent)) { + _weaken(_pthread_onfork_parent)(); + } STRACE("fork() → %d% m", ax); } + _unassert(!sigprocmask(SIG_SETMASK, &old, 0)); return ax; } diff --git a/libc/runtime/memtrack.internal.h b/libc/runtime/memtrack.internal.h index 5c14ca304..80454f27f 100644 --- a/libc/runtime/memtrack.internal.h +++ b/libc/runtime/memtrack.internal.h @@ -11,19 +11,19 @@ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ -#define kAutomapStart 0x100080040000 -#define kAutomapSize (kMemtrackStart - kAutomapStart) -#define kMemtrackStart 0x1fe7fffc0000 -#define kMemtrackSize (0x1ffffffc0000 - kMemtrackStart) -#define kFixedmapStart 0x300000040000 -#define kFixedmapSize (0x400000040000 - kFixedmapStart) -#define kMemtrackFdsStart 0x6fe000040000 -#define kMemtrackFdsSize (0x6feffffc0000 - kMemtrackFdsStart) -#define kMemtrackZiposStart 0x6fd000040000 -#define kMemtrackZiposSize (0x6fdffffc0000 - kMemtrackZiposStart) -#define kMemtrackNsyncStart 0x6fc000040000 -#define kMemtrackNsyncSize (0x6fcffffc0000 - kMemtrackNsyncStart) -#define kMemtrackGran (!IsAsan() ? FRAMESIZE : FRAMESIZE * 8) +#define kAutomapStart 0x100080040000 +#define kAutomapSize (kMemtrackStart - kAutomapStart) +#define kMemtrackStart 0x1fe7fffc0000 +#define kMemtrackSize (0x1ffffffc0000 - kMemtrackStart) +#define kFixedmapStart 0x300000040000 +#define kFixedmapSize (0x400000040000 - kFixedmapStart) +#define kMemtrackFdsStart 0x6fe000040000 +#define kMemtrackFdsSize (0x6feffffc0000 - kMemtrackFdsStart) +#define kMemtrackZiposStart 0x6fd000040000 +#define kMemtrackZiposSize (0x6fdffffc0000 - kMemtrackZiposStart) +#define kMemtrackKmallocStart 0x6fc000040000 +#define kMemtrackKmallocSize (0x6fcffffc0000 - kMemtrackKmallocStart) +#define kMemtrackGran (!IsAsan() ? FRAMESIZE : FRAMESIZE * 8) struct MemoryInterval { int x; @@ -47,6 +47,7 @@ extern struct MemoryIntervals _mmi; void __mmi_lock(void); void __mmi_unlock(void); +void __mmi_funlock(void); bool IsMemtracked(int, int); void PrintSystemMappings(int); unsigned FindMemoryInterval(const struct MemoryIntervals *, int) nosideeffect; @@ -95,9 +96,9 @@ forceinline pureconst bool IsZiposFrame(int x) { x <= (int)((kMemtrackZiposStart + kMemtrackZiposSize - 1) >> 16); } -forceinline pureconst bool IsNsyncFrame(int x) { - return (int)(kMemtrackNsyncStart >> 16) <= x && - x <= (int)((kMemtrackNsyncStart + kMemtrackNsyncSize - 1) >> 16); +forceinline pureconst bool IsKmallocFrame(int x) { + return (int)(kMemtrackKmallocStart >> 16) <= x && + x <= (int)((kMemtrackKmallocStart + kMemtrackKmallocSize - 1) >> 16); } forceinline pureconst bool IsShadowFrame(int x) { diff --git a/libc/runtime/memtrack64.txt b/libc/runtime/memtrack64.txt index 9221a41f5..44ff03a37 100644 --- a/libc/runtime/memtrack64.txt +++ b/libc/runtime/memtrack64.txt @@ -1808,7 +1808,7 @@ 6f900000-6f9fffff 64gb free 6fa00000-6fafffff 64gb free 6fb00000-6fbfffff 64gb free -6fc00004-6fcffffb 64gb nsync +6fc00004-6fcffffb 64gb kmalloc 6fd00004-6fdffffb 64gb zipos 6fe00004-6feffffb 64gb g_fds 6ff00004-70000003 64gb free diff --git a/libc/runtime/vfork.S b/libc/runtime/vfork.S index 7b4e4ba89..7bb00df25 100644 --- a/libc/runtime/vfork.S +++ b/libc/runtime/vfork.S @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/dce.h" #include "libc/intrin/strace.internal.h" +#include "libc/thread/tls.h" #include "libc/macros.internal.h" .privileged @@ -26,7 +27,10 @@ // This is the same as fork() except it's optimized for the case // where the caller invokes execve() immediately afterwards. You // can also call functions like close(), dup2(), etc. Call _exit -// but don't call exit. Look for vforksafe function annotations. +// but don't call exit. Look for vforksafe function annotations, +// For example pthread mutexes are @vforksafe because they don't +// do anything in a vfork()'d child process. TLS memory must not +// be disabled (it's enabled by default) since vfork() needs it. // // Do not make the assumption that the parent is suspended until // the child terminates since this impl calls fork() on Windows, @@ -34,8 +38,10 @@ // // @return pid of child process or 0 if forked process // @returnstwice +// @threadsafe // @vforksafe -vfork: xor %edi,%edi # dwCreationFlags +vfork: call __require_tls + xor %edi,%edi # dwCreationFlags #ifdef __SANITIZE_ADDRESS__ jmp fork # TODO: asan and vfork don't mix? .endfn vfork,globl @@ -56,45 +62,31 @@ vfork: xor %edi,%edi # dwCreationFlags ezlea .Llog,di call __stracef #endif /* SYSDEBUG */ - mov __NR_vfork(%rip),%eax - mov __errno(%rip),%r8d # avoid question of @vforksafe errno + mov %fs:0,%r9 # get thread information block + mov 0x3c(%r9),%r8d # avoid question of @vforksafe errno pop %rsi # saves return address in a register + mov __NR_vfork(%rip),%eax #if SupportsBsd() - testb IsBsd() - jnz vfork.bsd + clc #endif syscall +#if SupportsBsd() + jnc 0f + neg %rax +0: +#endif push %rsi # note it happens twice in same page -#if SupportsLinux() cmp $-4095,%eax jae systemfive_error -#endif -0: mov %r8d,__errno(%rip) - ezlea __vforked,di + mov %r8d,0x3c(%r9) # restore errno test %eax,%eax - jz 1f - decl (%rdi) - jns 2f # openbsd doesn't actually share mem -1: incl (%rdi) -2: ret + jnz .Lprnt +.Lchld: orb $TIB_FLAG_VFORKED,0x40(%r9) + ret +.Lprnt: andb $~TIB_FLAG_VFORKED,0x40(%r9) + ret .endfn vfork,globl -#if SupportsBsd() -vfork.bsd: - syscall - push %rsi - jc systemfive_errno -#if SupportsXnu() - testb IsXnu() - jz 0b - neg %edx # edx is 0 for parent and 1 for child - not %edx # eax always returned with childs pid - and %edx,%eax -#endif /* XNU */ - jmp 0b - .endfn vfork.bsd -#endif /* BSD */ - #ifdef SYSDEBUG .rodata.str1.1 .Llog: .ascii STRACE_PROLOGUE diff --git a/libc/stdio/dirstream.c b/libc/stdio/dirstream.c index 262c839bc..72d6f8f22 100644 --- a/libc/stdio/dirstream.c +++ b/libc/stdio/dirstream.c @@ -117,6 +117,8 @@ struct dirent_netbsd { char d_name[512]; }; +// TODO(jart): wipe these locks when forking + void _lockdir(DIR *dir) { pthread_mutex_lock(&dir->lock); } diff --git a/libc/stdio/fflush_unlocked.c b/libc/stdio/fflush_unlocked.c index 313234f39..1c71d90b6 100644 --- a/libc/stdio/fflush_unlocked.c +++ b/libc/stdio/fflush_unlocked.c @@ -38,6 +38,43 @@ 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((pthread_mutex_t *)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((pthread_mutex_t *)f->lock); + } + } + __fflush_unlock(); +} + +static void __stdio_fork_child(void) { + FILE *f; + pthread_mutex_t *m; + for (int i = __fflush.handles.i; i--;) { + if ((f = __fflush.handles.p[i])) { + m = (pthread_mutex_t *)f->lock; + bzero(m, sizeof(*m)); + m->_type = PTHREAD_MUTEX_RECURSIVE; + } + } + pthread_mutex_init(&__fflush_lock_obj, 0); +} + +__attribute__((__constructor__)) static void __stdio_init(void) { + pthread_atfork(__stdio_fork_prepare, __stdio_fork_parent, __stdio_fork_child); +} + /** * Blocks until data from stream buffer is written out. * diff --git a/libc/stdio/posix_spawn.c b/libc/stdio/posix_spawn.c index cc0e3f475..a754e25bf 100644 --- a/libc/stdio/posix_spawn.c +++ b/libc/stdio/posix_spawn.c @@ -24,7 +24,7 @@ #include "libc/runtime/runtime.h" #include "libc/stdio/posix_spawn.h" #include "libc/stdio/posix_spawn.internal.h" -#include "libc/thread/thread.h" +#include "libc/sysv/consts/sig.h" #include "libc/thread/tls.h" static int RunFileActions(struct _posix_faction *a) { @@ -54,7 +54,11 @@ static int RunFileActions(struct _posix_faction *a) { } /** - * Spawns process the POSIX way. + * Spawns process, the POSIX way. + * + * This function provides an API for vfork() that's intended to be less + * terrifying to the uninitiated, since it only lets you define actions + * that are @vforksafe. This function requires TLS not be disabled. * * @param pid if non-null shall be set to child pid on success * @param path is resolved path of program which is not `$PATH` searched @@ -63,33 +67,40 @@ static int RunFileActions(struct _posix_faction *a) { * @param envp is environment variables, or `environ` if null * @return 0 on success or error number on failure * @see posix_spawnp() for `$PATH` searching + * @tlsrequired + * @threadsafe */ int posix_spawn(int *pid, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[]) { - int s, child; - sigset_t allsigs; - struct sigaction dfl; - if (!(child = _weaken(pthread_create) ? fork() : vfork())) { + short flags = 0; + sigset_t sigmask; + int s, child, policy; + struct sched_param param; + struct sigaction dfl = {0}; + if (!(child = vfork())) { if (attrp && *attrp) { - if ((*attrp)->flags & POSIX_SPAWN_SETPGROUP) { - if (setpgid(0, (*attrp)->pgroup)) _Exit(127); + posix_spawnattr_getflags(attrp, &flags); + if (flags & POSIX_SPAWN_SETPGROUP) { + if (setpgid(0, (*attrp)->pgroup)) { + _Exit(127); + } } - if ((*attrp)->flags & POSIX_SPAWN_SETSIGMASK) { - sigprocmask(SIG_SETMASK, &(*attrp)->sigmask, 0); + if (flags & POSIX_SPAWN_SETSIGMASK) { + posix_spawnattr_getsigmask(attrp, &sigmask); + sigprocmask(SIG_SETMASK, &sigmask, 0); } - if ((*attrp)->flags & POSIX_SPAWN_RESETIDS) { + if (flags & POSIX_SPAWN_RESETIDS) { setuid(getuid()); setgid(getgid()); } - if ((*attrp)->flags & POSIX_SPAWN_SETSIGDEF) { - dfl.sa_handler = SIG_DFL; - dfl.sa_flags = 0; - sigfillset(&allsigs); - for (s = 0; sigismember(&allsigs, s); s++) { + if (flags & POSIX_SPAWN_SETSIGDEF) { + for (s = 1; s < 32; s++) { if (sigismember(&(*attrp)->sigdefault, s)) { - if (sigaction(s, &dfl, 0) == -1) _Exit(127); + if (sigaction(s, &dfl, 0) == -1) { + _Exit(127); + } } } } @@ -100,15 +111,17 @@ int posix_spawn(int *pid, const char *path, } } if (attrp && *attrp) { - if ((*attrp)->flags & POSIX_SPAWN_SETSCHEDULER) { - if (sched_setscheduler(0, (*attrp)->schedpolicy, - &(*attrp)->schedparam) == -1) { - if (errno != ENOSYS) _Exit(127); + if (flags & POSIX_SPAWN_SETSCHEDULER) { + posix_spawnattr_getschedpolicy(attrp, &policy); + posix_spawnattr_getschedparam(attrp, ¶m); + if (sched_setscheduler(0, policy, ¶m) == -1) { + _Exit(127); } } - if ((*attrp)->flags & POSIX_SPAWN_SETSCHEDPARAM) { - if (sched_setparam(0, &(*attrp)->schedparam) == -1) { - if (errno != ENOSYS) _Exit(127); + if (flags & POSIX_SPAWN_SETSCHEDPARAM) { + posix_spawnattr_getschedparam(attrp, ¶m); + if (sched_setparam(0, ¶m) == -1) { + _Exit(127); } } } diff --git a/libc/stdio/posix_spawnattr.c b/libc/stdio/posix_spawnattr.c index 44a8c29ad..f16603a3e 100644 --- a/libc/stdio/posix_spawnattr.c +++ b/libc/stdio/posix_spawnattr.c @@ -16,8 +16,10 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" #include "libc/calls/calls.h" #include "libc/calls/struct/sigset.h" +#include "libc/dce.h" #include "libc/errno.h" #include "libc/mem/mem.h" #include "libc/stdio/posix_spawn.h" @@ -32,19 +34,15 @@ * @raise ENOMEM if we require more vespene gas */ int posix_spawnattr_init(posix_spawnattr_t *attr) { - int e, rc; + int rc, e = errno; struct _posix_spawna *a; - e = errno; - errno = 0; - if ((a = calloc(1, sizeof(*a)))) { - a->flags = 0; - a->pgroup = 0; - sigemptyset(&a->sigdefault); - a->schedpolicy = sched_getscheduler(0); - sched_getparam(0, &a->schedparam); + if ((a = calloc(1, sizeof(struct _posix_spawna)))) { + *attr = a; + rc = 0; + } else { + rc = errno; + errno = e; } - rc = errno; - errno = e; return rc; } @@ -79,7 +77,9 @@ int posix_spawnattr_getflags(const posix_spawnattr_t *attr, short *flags) { * Sets posix_spawn() flags. * * Setting these flags is needed in order for the other setters in this - * function to take effect. + * function to take effect. If a flag is known but unsupported by the + * host platform, it'll be silently removed from the flags. You can + * check for this by calling the getter afterwards. * * @param attr was initialized by posix_spawnattr_init() * @param flags may have any of the following @@ -93,6 +93,9 @@ int posix_spawnattr_getflags(const posix_spawnattr_t *attr, short *flags) { * @raise EINVAL if `flags` has invalid bits */ int posix_spawnattr_setflags(posix_spawnattr_t *attr, short flags) { + if (!(IsLinux() || IsFreebsd() || IsNetbsd())) { + flags &= ~(POSIX_SPAWN_SETSCHEDPARAM | POSIX_SPAWN_SETSCHEDULER); + } if (flags & ~(POSIX_SPAWN_RESETIDS | POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSCHEDPARAM | POSIX_SPAWN_SETSCHEDULER)) { @@ -121,14 +124,23 @@ int posix_spawnattr_setpgroup(posix_spawnattr_t *attr, int pgroup) { * @param attr was initialized by posix_spawnattr_init() * @param schedpolicy receives the result * @return 0 on success, or errno on error + * @raise ENOSYS if platform support isn't available */ int posix_spawnattr_getschedpolicy(const posix_spawnattr_t *attr, int *schedpolicy) { - if (!(*attr)->schedpolicy_isset) { - (*attr)->schedpolicy = sched_getscheduler(0); - (*attr)->schedpolicy_isset = true; + int rc, e = errno; + struct _posix_spawna *a = *(/*unconst*/ posix_spawnattr_t *)attr; + if (!a->schedpolicy_isset) { + rc = sched_getscheduler(0); + if (rc == -1) { + rc = errno; + errno = e; + return rc; + } + a->schedpolicy = rc; + a->schedpolicy_isset = true; } - *schedpolicy = (*attr)->schedpolicy; + *schedpolicy = a->schedpolicy; return 0; } @@ -156,14 +168,22 @@ int posix_spawnattr_setschedpolicy(posix_spawnattr_t *attr, int schedpolicy) { * @param attr was initialized by posix_spawnattr_init() * @param schedparam receives the result * @return 0 on success, or errno on error + * @raise ENOSYS if platform support isn't available */ int posix_spawnattr_getschedparam(const posix_spawnattr_t *attr, struct sched_param *schedparam) { - if (!(*attr)->schedparam_isset) { - sched_getparam(0, &(*attr)->schedparam); - (*attr)->schedparam_isset = true; + int rc, e = errno; + struct _posix_spawna *a = *(/*unconst*/ posix_spawnattr_t *)attr; + if (!a->schedparam_isset) { + rc = sched_getparam(0, &a->schedparam); + if (rc == -1) { + rc = errno; + errno = e; + return rc; + } + a->schedparam_isset = true; } - *schedparam = (*attr)->schedparam; + *schedparam = a->schedparam; return 0; } @@ -183,16 +203,32 @@ int posix_spawnattr_setschedparam(posix_spawnattr_t *attr, return 0; } +/** + * Gets signal mask for sigprocmask() in child process. + * + * If the setter wasn't called then this function will return the + * scheduling parameter of the current process. + * + * @return 0 on success, or errno on error + */ int posix_spawnattr_getsigmask(const posix_spawnattr_t *attr, sigset_t *sigmask) { - if (!(*attr)->sigmask_isset) { - sigprocmask(SIG_SETMASK, 0, &(*attr)->sigmask); - (*attr)->sigmask_isset = true; + struct _posix_spawna *a = *(/*unconst*/ posix_spawnattr_t *)attr; + if (!a->sigmask_isset) { + _npassert(!sigprocmask(SIG_SETMASK, 0, &a->sigmask)); + a->sigmask_isset = true; } - *sigmask = (*attr)->sigmask; + *sigmask = a->sigmask; return 0; } +/** + * Specifies signal mask for sigprocmask() in child process. + * + * Signal masks are inherited by default. Use this to change it. + * + * @return 0 on success, or errno on error + */ int posix_spawnattr_setsigmask(posix_spawnattr_t *attr, const sigset_t *sigmask) { (*attr)->sigmask = *sigmask; @@ -200,12 +236,22 @@ int posix_spawnattr_setsigmask(posix_spawnattr_t *attr, return 0; } +/** + * Retrieves which signals will be restored to `SIG_DFL`. + * + * @return 0 on success, or errno on error + */ int posix_spawnattr_getsigdefault(const posix_spawnattr_t *attr, sigset_t *sigdefault) { *sigdefault = (*attr)->sigdefault; return 0; } +/** + * Specifies which signals should be restored to `SIG_DFL`. + * + * @return 0 on success, or errno on error + */ int posix_spawnattr_setsigdefault(posix_spawnattr_t *attr, const sigset_t *sigdefault) { (*attr)->sigdefault = *sigdefault; diff --git a/libc/str/blake2.c b/libc/str/blake2.c index 551f81f19..5a663e112 100644 --- a/libc/str/blake2.c +++ b/libc/str/blake2.c @@ -166,6 +166,10 @@ int BLAKE2B256_Final(struct Blake2b *b2b, * blake2b256 n=256 1 ns/byte 662 mb/s * blake2b256 n=22851 1 ns/byte 683 mb/s * + * @param data is binary memory to hash + * @param len is bytes in `data` + * @param out receives 32 byte binary digest + * @return 0 on success (always successful) */ int BLAKE2B256(const void *data, size_t len, uint8_t out[BLAKE2B256_DIGEST_LENGTH]) { diff --git a/libc/thread/sem_unlink.c b/libc/str/hexpcpy.c similarity index 76% rename from libc/thread/sem_unlink.c rename to libc/str/hexpcpy.c index 7d59b49e8..9ae018401 100644 --- a/libc/thread/sem_unlink.c +++ b/libc/str/hexpcpy.c @@ -16,20 +16,25 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/calls.h" -#include "libc/thread/semaphore.h" -#include "libc/thread/semaphore.internal.h" +#include "libc/str/str.h" /** - * Removes named semaphore. + * Turns data into lowercase hex. * - * @param name can be absolute path or should be component w/o slashes - * @return 0 on success, or -1 w/ errno - * @raise EPERM if pledge() is in play w/o `cpath` promise - * @raise ENOENT if named semaphore doesn't exist - * @raise EACCES if permission is denied + * This routine always writes a nul terminator, even if `n` is zero. + * There's no failure condition for this function. + * + * @param s must have `n*2+1` bytes + * @param p must have `n` bytes + * @param n is byte length of p + * @return pointer to nul byte in `s` */ -int sem_unlink(const char *name) { - char path[PATH_MAX]; - return unlink(__sem_name(name, path)); +char *hexpcpy(char *restrict s, const void *restrict p, size_t n) { + const char *d, *e; + for (d = p, e = d + n; d < e; ++d) { + *s++ = "0123456789abcdef"[(*d >> 4) & 15]; + *s++ = "0123456789abcdef"[(*d >> 0) & 15]; + } + *s = 0; + return s; } diff --git a/libc/str/str.h b/libc/str/str.h index 571dc03b4..0ee028e81 100644 --- a/libc/str/str.h +++ b/libc/str/str.h @@ -46,6 +46,7 @@ void *memset(void *, int, size_t) memcpyesque; void *memmove(void *, const void *, size_t) memcpyesque; void *memcpy(void *restrict, const void *restrict, size_t) memcpyesque; void *mempcpy(void *restrict, const void *restrict, size_t) memcpyesque; +char *hexpcpy(char *restrict, const void *restrict, size_t) memcpyesque; void *memccpy(void *restrict, const void *restrict, int, size_t) memcpyesque; void bcopy(const void *, void *, size_t) memcpyesque; void explicit_bzero(void *, size_t); diff --git a/libc/sysv/consts.sh b/libc/sysv/consts.sh index 8fd9fd3f3..68ce27681 100755 --- a/libc/sysv/consts.sh +++ b/libc/sysv/consts.sh @@ -1107,32 +1107,6 @@ syscon pf PF_VSOCK 40 0 0 0 0 0 syscon pf PF_WANPIPE 25 0 0 0 0 0 syscon pf PF_X25 9 0 0 0 0 0 -# Eric Allman's exit() codes -# -# - Broadly supported style guideline; -# - Dating back to 1980 in 4.0BSD; -# - That won't be standardized. -# -# group name GNU/Systemd XNU's Not UNIX! FreeBSD OpenBSD NetBSD The New Technology Commentary -syscon ex EX_OK 0 0 0 0 0 0 # consensus -syscon ex EX_USAGE 64 64 64 64 64 64 # unix consensus & force NT -syscon ex EX_DATAERR 65 65 65 65 65 65 # unix consensus & force NT -syscon ex EX_NOINPUT 66 66 66 66 66 66 # unix consensus & force NT -syscon ex EX_NOUSER 67 67 67 67 67 67 # unix consensus & force NT -syscon ex EX_NOHOST 68 68 68 68 68 68 # unix consensus & force NT -syscon ex EX_UNAVAILABLE 69 69 69 69 69 69 # unix consensus & force NT -syscon ex EX_SOFTWARE 70 70 70 70 70 70 # unix consensus & force NT -syscon ex EX_OSERR 71 71 71 71 71 71 # unix consensus & force NT -syscon ex EX_OSFILE 72 72 72 72 72 72 # unix consensus & force NT -syscon ex EX_CANTCREAT 73 73 73 73 73 73 # unix consensus & force NT -syscon ex EX_IOERR 74 74 74 74 74 74 # unix consensus & force NT -syscon ex EX_TEMPFAIL 75 75 75 75 75 75 # unix consensus & force NT -syscon ex EX_PROTOCOL 76 76 76 76 76 76 # unix consensus & force NT -syscon ex EX_NOPERM 77 77 77 77 77 77 # unix consensus & force NT -syscon ex EX_CONFIG 78 78 78 78 78 78 # unix consensus & force NT -syscon ex EX__BASE 64 64 64 64 64 64 # unix consensus & force NT -syscon ex EX__MAX 78 78 78 78 78 78 # unix consensus & force NT - # getdents() constants # # group name GNU/Systemd XNU's Not UNIX! FreeBSD OpenBSD NetBSD The New Technology Commentary diff --git a/libc/sysv/consts/EX_CANTCREAT.s b/libc/sysv/consts/EX_CANTCREAT.s deleted file mode 100644 index 26ec59250..000000000 --- a/libc/sysv/consts/EX_CANTCREAT.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/consts/syscon.internal.inc" -.syscon ex,EX_CANTCREAT,73,73,73,73,73,73 diff --git a/libc/sysv/consts/EX_CONFIG.s b/libc/sysv/consts/EX_CONFIG.s deleted file mode 100644 index 56441ab29..000000000 --- a/libc/sysv/consts/EX_CONFIG.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/consts/syscon.internal.inc" -.syscon ex,EX_CONFIG,78,78,78,78,78,78 diff --git a/libc/sysv/consts/EX_NOHOST.s b/libc/sysv/consts/EX_NOHOST.s deleted file mode 100644 index e4d9508af..000000000 --- a/libc/sysv/consts/EX_NOHOST.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/consts/syscon.internal.inc" -.syscon ex,EX_NOHOST,68,68,68,68,68,68 diff --git a/libc/sysv/consts/EX_NOINPUT.s b/libc/sysv/consts/EX_NOINPUT.s deleted file mode 100644 index 9a46de4c3..000000000 --- a/libc/sysv/consts/EX_NOINPUT.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/consts/syscon.internal.inc" -.syscon ex,EX_NOINPUT,66,66,66,66,66,66 diff --git a/libc/sysv/consts/EX_NOPERM.s b/libc/sysv/consts/EX_NOPERM.s deleted file mode 100644 index 3256cc9d8..000000000 --- a/libc/sysv/consts/EX_NOPERM.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/consts/syscon.internal.inc" -.syscon ex,EX_NOPERM,77,77,77,77,77,77 diff --git a/libc/sysv/consts/EX_NOUSER.s b/libc/sysv/consts/EX_NOUSER.s deleted file mode 100644 index 52c305ac7..000000000 --- a/libc/sysv/consts/EX_NOUSER.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/consts/syscon.internal.inc" -.syscon ex,EX_NOUSER,67,67,67,67,67,67 diff --git a/libc/sysv/consts/EX_OK.s b/libc/sysv/consts/EX_OK.s deleted file mode 100644 index ef092f3d2..000000000 --- a/libc/sysv/consts/EX_OK.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/consts/syscon.internal.inc" -.syscon ex,EX_OK,0,0,0,0,0,0 diff --git a/libc/sysv/consts/EX_OSERR.s b/libc/sysv/consts/EX_OSERR.s deleted file mode 100644 index 0f09bbc11..000000000 --- a/libc/sysv/consts/EX_OSERR.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/consts/syscon.internal.inc" -.syscon ex,EX_OSERR,71,71,71,71,71,71 diff --git a/libc/sysv/consts/EX_OSFILE.s b/libc/sysv/consts/EX_OSFILE.s deleted file mode 100644 index b00a2bd5e..000000000 --- a/libc/sysv/consts/EX_OSFILE.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/consts/syscon.internal.inc" -.syscon ex,EX_OSFILE,72,72,72,72,72,72 diff --git a/libc/sysv/consts/EX_PROTOCOL.s b/libc/sysv/consts/EX_PROTOCOL.s deleted file mode 100644 index 94531ad09..000000000 --- a/libc/sysv/consts/EX_PROTOCOL.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/consts/syscon.internal.inc" -.syscon ex,EX_PROTOCOL,76,76,76,76,76,76 diff --git a/libc/sysv/consts/EX_SOFTWARE.s b/libc/sysv/consts/EX_SOFTWARE.s deleted file mode 100644 index 18365b1a2..000000000 --- a/libc/sysv/consts/EX_SOFTWARE.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/consts/syscon.internal.inc" -.syscon ex,EX_SOFTWARE,70,70,70,70,70,70 diff --git a/libc/sysv/consts/EX_TEMPFAIL.s b/libc/sysv/consts/EX_TEMPFAIL.s deleted file mode 100644 index 7c25d8b3b..000000000 --- a/libc/sysv/consts/EX_TEMPFAIL.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/consts/syscon.internal.inc" -.syscon ex,EX_TEMPFAIL,75,75,75,75,75,75 diff --git a/libc/sysv/consts/EX_UNAVAILABLE.s b/libc/sysv/consts/EX_UNAVAILABLE.s deleted file mode 100644 index 8415d66b2..000000000 --- a/libc/sysv/consts/EX_UNAVAILABLE.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/consts/syscon.internal.inc" -.syscon ex,EX_UNAVAILABLE,69,69,69,69,69,69 diff --git a/libc/sysv/consts/EX_USAGE.s b/libc/sysv/consts/EX_USAGE.s deleted file mode 100644 index ede4a0950..000000000 --- a/libc/sysv/consts/EX_USAGE.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/consts/syscon.internal.inc" -.syscon ex,EX_USAGE,64,64,64,64,64,64 diff --git a/libc/sysv/consts/EX__BASE.s b/libc/sysv/consts/EX__BASE.s deleted file mode 100644 index 965ebc2b1..000000000 --- a/libc/sysv/consts/EX__BASE.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/consts/syscon.internal.inc" -.syscon ex,EX__BASE,64,64,64,64,64,64 diff --git a/libc/sysv/consts/EX__MAX.s b/libc/sysv/consts/EX__MAX.s deleted file mode 100644 index 0705eca2e..000000000 --- a/libc/sysv/consts/EX__MAX.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/consts/syscon.internal.inc" -.syscon ex,EX__MAX,78,78,78,78,78,78 diff --git a/libc/sysv/consts/LINK_MAX.s b/libc/sysv/consts/LINK_MAX.s new file mode 100644 index 000000000..2ea57e842 --- /dev/null +++ b/libc/sysv/consts/LINK_MAX.s @@ -0,0 +1,2 @@ +.include "o/libc/sysv/consts/syscon.internal.inc" +.syscon limits,LINK_MAX,127,32767,32767,32767,32767,64 diff --git a/libc/sysv/consts/EX_DATAERR.s b/libc/sysv/consts/MAX_CANON.s similarity index 50% rename from libc/sysv/consts/EX_DATAERR.s rename to libc/sysv/consts/MAX_CANON.s index f623d121c..321b643fa 100644 --- a/libc/sysv/consts/EX_DATAERR.s +++ b/libc/sysv/consts/MAX_CANON.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon ex,EX_DATAERR,65,65,65,65,65,65 +.syscon limits,MAX_CANON,255,1024,255,255,255,255 diff --git a/libc/sysv/consts/EX_IOERR.s b/libc/sysv/consts/MAX_INPUT.s similarity index 50% rename from libc/sysv/consts/EX_IOERR.s rename to libc/sysv/consts/MAX_INPUT.s index 43f32f41c..757fa3440 100644 --- a/libc/sysv/consts/EX_IOERR.s +++ b/libc/sysv/consts/MAX_INPUT.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon ex,EX_IOERR,74,74,74,74,74,74 +.syscon limits,MAX_INPUT,255,1024,255,255,255,255 diff --git a/libc/sysv/consts/c.h b/libc/sysv/consts/c.h index 3deaf26de..05e4f4de6 100644 --- a/libc/sysv/consts/c.h +++ b/libc/sysv/consts/c.h @@ -1,25 +1,29 @@ #ifndef COSMOPOLITAN_LIBC_SYSV_CONSTS_CPIO_H_ #define COSMOPOLITAN_LIBC_SYSV_CONSTS_CPIO_H_ -#define C_IXOTH 0000001 -#define C_IWOTH 0000002 -#define C_IROTH 0000004 -#define C_IXGRP 0000010 -#define C_IWGRP 0000020 -#define C_IRGRP 0000040 -#define C_IXUSR 0000100 -#define C_IWUSR 0000200 -#define C_IRUSR 0000400 -#define C_ISVTX 0001000 -#define C_ISGID 0002000 -#define C_ISUID 0004000 -#define C_ISFIFO 0010000 -#define C_ISCHR 0020000 -#define C_ISDIR 0040000 -#define C_ISBLK 0060000 -#define C_ISREG 0100000 -#define C_ISCTG 0110000 -#define C_ISLNK 0120000 +#define MAGIC "070707" + +#define C_IRUSR 000400 +#define C_IWUSR 000200 +#define C_IXUSR 000100 +#define C_IRGRP 000040 +#define C_IWGRP 000020 +#define C_IXGRP 000010 +#define C_IROTH 000004 +#define C_IWOTH 000002 +#define C_IXOTH 000001 + +#define C_ISUID 004000 +#define C_ISGID 002000 +#define C_ISVTX 001000 + +#define C_ISBLK 060000 +#define C_ISCHR 020000 +#define C_ISDIR 040000 +#define C_ISFIFO 010000 #define C_ISSOCK 0140000 +#define C_ISLNK 0120000 +#define C_ISCTG 0110000 +#define C_ISREG 0100000 #endif /* COSMOPOLITAN_LIBC_SYSV_CONSTS_CPIO_H_ */ diff --git a/libc/sysv/consts/ex.h b/libc/sysv/consts/ex.h index 9fb1a6d17..58f7773e2 100644 --- a/libc/sysv/consts/ex.h +++ b/libc/sysv/consts/ex.h @@ -1,48 +1,32 @@ #ifndef COSMOPOLITAN_LIBC_SYSV_CONSTS_EX_H_ #define COSMOPOLITAN_LIBC_SYSV_CONSTS_EX_H_ -#include "libc/runtime/symbolic.h" -#if !(__ASSEMBLER__ + __LINKER__ + 0) -COSMOPOLITAN_C_START_ -extern const int EX_CANTCREAT; -extern const int EX_CONFIG; -extern const int EX_DATAERR; -extern const int EX_IOERR; -extern const int EX_NOHOST; -extern const int EX_NOINPUT; -extern const int EX_NOPERM; -extern const int EX_NOUSER; -extern const int EX_OK; -extern const int EX_OSERR; -extern const int EX_OSFILE; -extern const int EX_PROTOCOL; -extern const int EX_SOFTWARE; -extern const int EX_TEMPFAIL; -extern const int EX_UNAVAILABLE; -extern const int EX_USAGE; -extern const int EX__BASE; -extern const int EX__MAX; +/** + * @fileoverview Eric Allman's exit() codes + * + * - Broadly supported style guideline; + * - Dating back to 1980 in 4.0BSD; + * - That won't be standardized. + * + */ -COSMOPOLITAN_C_END_ -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ - -#define EX_CANTCREAT LITERALLY(73) -#define EX_CONFIG LITERALLY(78) -#define EX_DATAERR LITERALLY(65) -#define EX_IOERR LITERALLY(74) -#define EX_NOHOST LITERALLY(68) -#define EX_NOINPUT LITERALLY(66) -#define EX_NOPERM LITERALLY(77) -#define EX_NOUSER LITERALLY(67) -#define EX_OK LITERALLY(0) -#define EX_OSERR LITERALLY(71) -#define EX_OSFILE LITERALLY(72) -#define EX_PROTOCOL LITERALLY(76) -#define EX_SOFTWARE LITERALLY(70) -#define EX_TEMPFAIL LITERALLY(75) -#define EX_UNAVAILABLE LITERALLY(69) -#define EX_USAGE LITERALLY(64) -#define EX__BASE LITERALLY(64) -#define EX__MAX LITERALLY(78) +#define EX_CANTCREAT 73 +#define EX_CONFIG 78 +#define EX_DATAERR 65 +#define EX_IOERR 74 +#define EX_NOHOST 68 +#define EX_NOINPUT 66 +#define EX_NOPERM 77 +#define EX_NOUSER 67 +#define EX_OK 0 +#define EX_OSERR 71 +#define EX_OSFILE 72 +#define EX_PROTOCOL 76 +#define EX_SOFTWARE 70 +#define EX_TEMPFAIL 75 +#define EX_UNAVAILABLE 69 +#define EX_USAGE 64 +#define EX__BASE 64 +#define EX__MAX 78 #endif /* COSMOPOLITAN_LIBC_SYSV_CONSTS_EX_H_ */ diff --git a/libc/testlib/testrunner.c b/libc/testlib/testrunner.c index bb30f8a4c..f044e3a9c 100644 --- a/libc/testlib/testrunner.c +++ b/libc/testlib/testrunner.c @@ -63,7 +63,7 @@ void testlib_finish(void) { void testlib_error_enter(const char *file, const char *func) { atomic_fetch_sub_explicit(&__ftrace, 1, memory_order_relaxed); atomic_fetch_sub_explicit(&__strace, 1, memory_order_relaxed); - if (!__vforked) pthread_mutex_lock(&testlib_error_lock); + pthread_mutex_lock(&testlib_error_lock); if (!IsWindows()) sys_getpid(); /* make strace easier to read */ if (!IsWindows()) sys_getpid(); if (g_testlib_shoulddebugbreak) { diff --git a/libc/thread/posixthread.internal.h b/libc/thread/posixthread.internal.h index de3e24ae4..95ed0400d 100644 --- a/libc/thread/posixthread.internal.h +++ b/libc/thread/posixthread.internal.h @@ -1,6 +1,7 @@ #ifndef COSMOPOLITAN_LIBC_THREAD_POSIXTHREAD_INTERNAL_H_ #define COSMOPOLITAN_LIBC_THREAD_POSIXTHREAD_INTERNAL_H_ #include "libc/calls/struct/sched_param.h" +#include "libc/calls/struct/sigset.h" #include "libc/runtime/runtime.h" #include "libc/thread/thread.h" #include "libc/thread/tls.h" @@ -15,6 +16,8 @@ COSMOPOLITAN_C_START_ * @fileoverview Cosmopolitan POSIX Thread Internals */ +typedef void (*atfork_f)(void); + // LEGAL TRANSITIONS ┌──> TERMINATED // pthread_create ─┬─> JOINABLE ─┴┬─> DETACHED ──> ZOMBIE // └──────────────┘ @@ -73,6 +76,7 @@ struct PosixThread { struct CosmoTib *tib; // middle of tls allocation jmp_buf exiter; // for pthread_exit pthread_attr_t attr; + sigset_t sigmask; struct _pthread_cleanup_buffer *cleanup; }; @@ -82,8 +86,7 @@ hidden extern uint64_t _pthread_key_usage[(PTHREAD_KEYS_MAX + 63) / 64]; hidden extern pthread_key_dtor _pthread_key_dtor[PTHREAD_KEYS_MAX]; hidden extern _Thread_local void *_pthread_keys[PTHREAD_KEYS_MAX]; -void _pthread_atfork(int) hidden; -void _pthread_funlock(pthread_mutex_t *, int) hidden; +int _pthread_atfork(atfork_f, atfork_f, atfork_f) hidden; int _pthread_reschedule(struct PosixThread *) hidden; int _pthread_setschedparam_freebsd(int, int, const struct sched_param *) hidden; void _pthread_free(struct PosixThread *) hidden; @@ -91,9 +94,15 @@ void _pthread_cleanup(struct PosixThread *) hidden; void _pthread_ungarbage(void) hidden; void _pthread_wait(struct PosixThread *) hidden; void _pthread_zombies_add(struct PosixThread *) hidden; +void _pthread_zombies_purge(void) hidden; void _pthread_zombies_decimate(void) hidden; void _pthread_zombies_harvest(void) hidden; void _pthread_key_destruct(void *[PTHREAD_KEYS_MAX]) hidden; +void _pthread_key_lock(void) hidden; +void _pthread_key_unlock(void) hidden; +void _pthread_onfork_prepare(void) hidden; +void _pthread_onfork_parent(void) hidden; +void _pthread_onfork_child(void) hidden; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/thread/pthread_atfork.c b/libc/thread/pthread_atfork.c new file mode 100644 index 000000000..fae2ac811 --- /dev/null +++ b/libc/thread/pthread_atfork.c @@ -0,0 +1,79 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2022 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" +#include "libc/intrin/kmalloc.h" +#include "libc/thread/posixthread.internal.h" +#include "libc/thread/thread.h" +#include "libc/thread/tls.h" + +static struct AtForks { + pthread_mutex_t lock; + struct AtFork { + struct AtFork *p[2]; + atfork_f f[3]; + } * list; +} _atforks; + +static void _pthread_onfork(int i) { + struct AtFork *a; + struct PosixThread *pt; + _unassert(0 <= i && i <= 2); + if (!i) pthread_mutex_lock(&_atforks.lock); + for (a = _atforks.list; a; a = a->p[!i]) { + if (a->f[i]) a->f[i](); + _atforks.list = a; + } + if (i) pthread_mutex_unlock(&_atforks.lock); + if (i == 2) { + _pthread_zombies_purge(); + if (__tls_enabled) { + pt = (struct PosixThread *)__get_tls()->tib_pthread; + pt->flags |= PT_MAINTHREAD; + } + } +} + +void _pthread_onfork_prepare(void) { + _pthread_onfork(0); +} + +void _pthread_onfork_parent(void) { + _pthread_onfork(1); +} + +void _pthread_onfork_child(void) { + _pthread_onfork(2); +} + +int _pthread_atfork(atfork_f prepare, atfork_f parent, atfork_f child) { + int rc; + struct AtFork *a; + a = kmalloc(sizeof(struct AtFork)); + 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); + rc = 0; + return rc; +} diff --git a/libc/thread/pthread_create.c b/libc/thread/pthread_create.c index b8fdb59e5..27429b8f9 100644 --- a/libc/thread/pthread_create.c +++ b/libc/thread/pthread_create.c @@ -21,6 +21,7 @@ #include "libc/calls/sched-sysv.internal.h" #include "libc/calls/state.internal.h" #include "libc/calls/struct/sigaltstack.h" +#include "libc/calls/struct/sigset.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" #include "libc/errno.h" @@ -41,6 +42,7 @@ #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/sysv/errfuns.h" #include "libc/thread/posixthread.internal.h" @@ -52,6 +54,7 @@ STATIC_YOINK("nsync_mu_lock"); STATIC_YOINK("nsync_mu_unlock"); +STATIC_YOINK("_pthread_atfork"); #define MAP_ANON_OPENBSD 0x1000 #define MAP_STACK_OPENBSD 0x4000 @@ -76,30 +79,6 @@ void _pthread_free(struct PosixThread *pt) { free(pt); } -void _pthread_funlock(pthread_mutex_t *mu, int parent_tid) { - if (mu->_type == PTHREAD_MUTEX_NORMAL || - (atomic_load_explicit(&mu->_lock, memory_order_relaxed) && - mu->_owner != parent_tid)) { - atomic_store_explicit(&mu->_lock, 0, memory_order_relaxed); - mu->_nsync = 0; - mu->_depth = 0; - mu->_owner = 0; - } -} - -void _pthread_atfork(int parent_tid) { - FILE *f; - if (_weaken(dlmalloc_atfork)) _weaken(dlmalloc_atfork)(); - _pthread_funlock(&__fds_lock_obj, parent_tid); - _pthread_funlock(&__sig_lock_obj, parent_tid); - _pthread_funlock(&__fflush_lock_obj, parent_tid); - for (int i = 0; i < __fflush.handles.i; ++i) { - if ((f = __fflush.handles.p[i])) { - _pthread_funlock((pthread_mutex_t *)f->lock, parent_tid); - } - } -} - static int PosixThread(void *arg, int tid) { struct PosixThread *pt = arg; enum PosixThreadStatus status; @@ -118,6 +97,7 @@ static int PosixThread(void *arg, int tid) { // set long jump handler so pthread_exit can bring control back here if (!setjmp(pt->exiter)) { __get_tls()->tib_pthread = (pthread_t)pt; + sigprocmask(SIG_SETMASK, &pt->sigmask, 0); pt->rc = pt->start_routine(pt->arg); // ensure pthread_cleanup_pop(), and pthread_exit() popped cleanup _npassert(!pt->cleanup); @@ -158,62 +138,12 @@ static int FixupCustomStackOnOpenbsd(pthread_attr_t *attr) { } } -/** - * Creates thread, e.g. - * - * void *worker(void *arg) { - * fputs(arg, stdout); - * return "there\n"; - * } - * - * int main() { - * void *result; - * pthread_t id; - * pthread_create(&id, 0, worker, "hi "); - * pthread_join(id, &result); - * fputs(result, stdout); - * } - * - * Here's the OSI model of threads in Cosmopolitan: - * - * ┌──────────────────┐ - * │ pthread_create() │ - Standard - * └─────────┬────────┘ Abstraction - * ┌─────────┴────────┐ - * │ clone() │ - Polyfill - * └─────────┬────────┘ - * ┌────────┬──┴┬─┬─┬─────────┐ - Kernel - * ┌─────┴─────┐ │ │ │┌┴──────┐ │ Interfaces - * │ sys_clone │ │ │ ││ tfork │ ┌┴─────────────┐ - * └───────────┘ │ │ │└───────┘ │ CreateThread │ - * ┌───────────────┴──┐│┌┴────────┐ └──────────────┘ - * │ bsdthread_create │││ thr_new │ - * └──────────────────┘│└─────────┘ - * ┌───────┴──────┐ - * │ _lwp_create │ - * └──────────────┘ - * - * @param thread if non-null is used to output the thread id - * upon successful completion - * @param attr points to launch configuration, or may be null - * to use sensible defaults; it must be initialized using - * pthread_attr_init() - * @param start_routine is your thread's callback function - * @param arg is an arbitrary value passed to `start_routine` - * @return 0 on success, or errno on error - * @raise EAGAIN if resources to create thread weren't available - * @raise EINVAL if `attr` was supplied and had unnaceptable data - * @raise EPERM if scheduling policy was requested and user account - * isn't authorized to use it - * @returnserrno - * @threadsafe - */ -errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr, - void *(*start_routine)(void *), void *arg) { +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; struct PosixThread *pt; - __require_tls(); - _pthread_zombies_decimate(); // create posix thread object if (!(pt = calloc(1, sizeof(struct PosixThread)))) { @@ -323,6 +253,7 @@ errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr, } // launch PosixThread(pt) in new thread + pt->sigmask = oldsigs; if (clone(PosixThread, pt->attr.__stackaddr, pt->attr.__stacksize - (IsOpenbsd() ? 16 : 0), CLONE_VM | CLONE_THREAD | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | @@ -340,3 +271,66 @@ errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr, } return 0; } + +/** + * Creates thread, e.g. + * + * void *worker(void *arg) { + * fputs(arg, stdout); + * return "there\n"; + * } + * + * int main() { + * void *result; + * pthread_t id; + * pthread_create(&id, 0, worker, "hi "); + * pthread_join(id, &result); + * fputs(result, stdout); + * } + * + * Here's the OSI model of threads in Cosmopolitan: + * + * ┌──────────────────┐ + * │ pthread_create() │ - Standard + * └─────────┬────────┘ Abstraction + * ┌─────────┴────────┐ + * │ clone() │ - Polyfill + * └─────────┬────────┘ + * ┌────────┬──┴┬─┬─┬─────────┐ - Kernel + * ┌─────┴─────┐ │ │ │┌┴──────┐ │ Interfaces + * │ sys_clone │ │ │ ││ tfork │ ┌┴─────────────┐ + * └───────────┘ │ │ │└───────┘ │ CreateThread │ + * ┌───────────────┴──┐│┌┴────────┐ └──────────────┘ + * │ bsdthread_create │││ thr_new │ + * └──────────────────┘│└─────────┘ + * ┌───────┴──────┐ + * │ _lwp_create │ + * └──────────────┘ + * + * @param thread if non-null is used to output the thread id + * upon successful completion + * @param attr points to launch configuration, or may be null + * to use sensible defaults; it must be initialized using + * pthread_attr_init() + * @param start_routine is your thread's callback function + * @param arg is an arbitrary value passed to `start_routine` + * @return 0 on success, or errno on error + * @raise EAGAIN if resources to create thread weren't available + * @raise EINVAL if `attr` was supplied and had unnaceptable data + * @raise EPERM if scheduling policy was requested and user account + * isn't authorized to use it + * @returnserrno + * @threadsafe + */ +errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*start_routine)(void *), void *arg) { + errno_t rc; + sigset_t blocksigs, oldsigs; + __require_tls(); + _pthread_zombies_decimate(); + sigfillset(&blocksigs); + _npassert(!sigprocmask(SIG_SETMASK, &blocksigs, &oldsigs)); + rc = pthread_create_impl(thread, attr, start_routine, arg, oldsigs); + _npassert(!sigprocmask(SIG_SETMASK, &oldsigs, 0)); + return rc; +} diff --git a/libc/thread/pthread_zombies.c b/libc/thread/pthread_zombies.c index 1fee44614..240b98024 100644 --- a/libc/thread/pthread_zombies.c +++ b/libc/thread/pthread_zombies.c @@ -23,9 +23,7 @@ #include "libc/thread/spawn.h" #include "libc/thread/thread.h" -/** - * @fileoverview Memory collector for detached threads. - */ +// TODO(jart): track all threads, not just zombies static struct Zombie { struct Zombie *next; @@ -70,6 +68,16 @@ void _pthread_zombies_harvest(void) { } } +void _pthread_zombies_purge(void) { + struct Zombie *z, *n; + while ((z = atomic_load_explicit(&_pthread_zombies, memory_order_relaxed))) { + _pthread_free(z->pt); + n = z->next; + free(z); + atomic_store_explicit(&_pthread_zombies, n, memory_order_relaxed); + } +} + __attribute__((__constructor__)) static void init(void) { atexit(_pthread_zombies_harvest); } diff --git a/libc/thread/sem_destroy.c b/libc/thread/sem_destroy.c index 668a632a0..3f2869814 100644 --- a/libc/thread/sem_destroy.c +++ b/libc/thread/sem_destroy.c @@ -16,17 +16,36 @@ │ 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/limits.h" +#include "libc/sysv/errfuns.h" #include "libc/thread/semaphore.h" /** * Destroys unnamed semaphore. * + * If `sem` was successfully initialized by sem_init() then + * sem_destroy() may be called multiple times, before it may be + * reinitialized again by calling sem_init(). + * + * Calling sem_destroy() on a semaphore created by sem_open() has + * undefined behavior. Using `sem` after calling sem_destroy() is + * undefined behavior that will cause semaphore APIs to either crash or + * raise `EINVAL` until `sem` is passed to sem_init() again. + * * @param sem was created by sem_init() * @return 0 on success, or -1 w/ errno + * @raise EINVAL if `sem` wasn't valid + * @raise EBUSY if `sem` has waiters */ int sem_destroy(sem_t *sem) { + int waiters; + _npassert(sem->sem_magic != SEM_MAGIC_NAMED); + if (sem->sem_magic != SEM_MAGIC_UNNAMED) return einval(); + waiters = atomic_load_explicit(&sem->sem_waiters, memory_order_relaxed); + _unassert(waiters >= 0); + if (waiters) return ebusy(); atomic_store_explicit(&sem->sem_value, INT_MIN, memory_order_relaxed); return 0; } diff --git a/libc/thread/sem_getvalue.c b/libc/thread/sem_getvalue.c index 28f34ad34..654e0e117 100644 --- a/libc/thread/sem_getvalue.c +++ b/libc/thread/sem_getvalue.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/calls/calls.h" #include "libc/intrin/atomic.h" #include "libc/thread/semaphore.h" @@ -27,6 +29,7 @@ * @return 0 on success, or -1 w/ errno */ int sem_getvalue(sem_t *sem, int *sval) { + _unassert(sem->sem_pshared || sem->sem_pid == getpid()); *sval = atomic_load_explicit(&sem->sem_value, memory_order_relaxed); return 0; } diff --git a/libc/thread/sem_init.c b/libc/thread/sem_init.c index 2c3ae4e70..69346e86d 100644 --- a/libc/thread/sem_init.c +++ b/libc/thread/sem_init.c @@ -16,6 +16,8 @@ │ 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/intrin/atomic.h" #include "libc/limits.h" #include "libc/sysv/errfuns.h" @@ -25,15 +27,25 @@ /** * Initializes unnamed semaphore. * + * Calling sem_init() on an already initialized semaphore is undefined. + * * @param sem should make its way to sem_destroy() if this succeeds - * @param pshared if semaphore may be shared between processes + * @param pshared if semaphore may be shared between processes, provided + * `sem` is backed by `mmap(MAP_ANONYMOUS | MAP_SHARED)` memory * @param value is initial count of semaphore * @return 0 on success, or -1 w/ errno * @raise EINVAL if `value` exceeds `SEM_VALUE_MAX` + * @raise EPERM on OpenBSD if `pshared` is true */ int sem_init(sem_t *sem, int pshared, unsigned value) { if (value > SEM_VALUE_MAX) return einval(); + // OpenBSD MAP_ANONYMOUS|MAP_SHARED memory is kind of busted. + // The OpenBSD implementation of sem_init() also EPERMs here. + if (IsOpenbsd() && pshared) return eperm(); + sem->sem_magic = SEM_MAGIC_UNNAMED; atomic_store_explicit(&sem->sem_value, value, memory_order_relaxed); - sem->sem_pshared = pshared; + sem->sem_pshared = !!pshared; + sem->sem_pid = getpid(); + sem->sem_waiters = 0; return 0; } diff --git a/libc/thread/sem_open.c b/libc/thread/sem_open.c index 63ecca554..16ddf0cf6 100644 --- a/libc/thread/sem_open.c +++ b/libc/thread/sem_open.c @@ -18,60 +18,298 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" #include "libc/calls/calls.h" +#include "libc/calls/struct/stat.h" +#include "libc/dce.h" +#include "libc/errno.h" +#include "libc/intrin/atomic.h" +#include "libc/intrin/nopl.internal.h" +#include "libc/intrin/strace.internal.h" +#include "libc/limits.h" +#include "libc/mem/mem.h" #include "libc/runtime/runtime.h" +#include "libc/str/str.h" #include "libc/sysv/consts/at.h" #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/prot.h" +#include "libc/sysv/errfuns.h" #include "libc/thread/semaphore.h" -#include "libc/thread/semaphore.internal.h" +#include "libc/thread/thread.h" +#include "libc/thread/tls.h" -/** - * Initializes and opens named semaphore. - * - * @param name can be absolute path or should be component w/o slashes - * @param oflga can have `O_CREAT` and/or `O_EXCL` - * @return semaphore object which needs sem_close(), or SEM_FAILED w/ errno - * @raise ENOTDIR if a directory component in `name` exists as non-directory - * @raise ENAMETOOLONG if symlink-resolved `name` length exceeds `PATH_MAX` - * @raise ENAMETOOLONG if component in `name` exists longer than `NAME_MAX` - * @raise ELOOP if `flags` had `O_NOFOLLOW` and `name` is a symbolic link - * @raise ENOSPC if file system is full when `name` would be `O_CREAT`ed - * @raise ELOOP if a loop was detected resolving components of `name` - * @raise EEXIST if `O_CREAT|O_EXCL` is used and semaphore exists - * @raise EACCES if we didn't have permission to create semaphore - * @raise EMFILE if process `RLIMIT_NOFILE` has been reached - * @raise ENFILE if system-wide file limit has been reached - * @raise EINTR if signal was delivered instead - */ -sem_t *sem_open(const char *name, int oflag, ...) { +static struct Semaphores { + pthread_once_t once; + pthread_mutex_t lock; + struct Semaphore { + struct Semaphore *next; + sem_t *sem; + char *path; + bool dead; + int refs; + } * list; +} g_semaphores; + +static void sem_open_lock(void) { + pthread_mutex_lock(&g_semaphores.lock); +} + +static void sem_open_unlock(void) { + pthread_mutex_unlock(&g_semaphores.lock); +} + +static void sem_open_funlock(void) { + pthread_mutex_init(&g_semaphores.lock, 0); +} + +static void sem_open_setup(void) { + sem_open_funlock(); + pthread_atfork(sem_open_lock, sem_open_unlock, sem_open_funlock); +} + +static void sem_open_init(void) { + pthread_once(&g_semaphores.once, sem_open_setup); +} + +#ifdef _NOPL0 +#define sem_open_lock() _NOPL0("__threadcalls", sem_open_lock) +#define sem_open_unlock() _NOPL0("__threadcalls", sem_open_unlock) +#endif + +static sem_t *sem_open_impl(const char *path, int oflag, unsigned mode, + unsigned value) { int fd; sem_t *sem; - va_list va; - unsigned mode; - char path[PATH_MAX]; - - va_start(va, oflag); - mode = va_arg(va, unsigned); - va_end(va); - + struct stat st; oflag |= O_RDWR | O_CLOEXEC; - if ((fd = openat(AT_FDCWD, __sem_name(name, path), oflag, mode)) == -1) { + if ((fd = openat(AT_FDCWD, path, oflag, mode)) == -1) { return SEM_FAILED; } - - if (ftruncate(fd, sizeof(sem_t)) == -1) { + _npassert(!fstat(fd, &st)); + if (st.st_size < PAGESIZE && ftruncate(fd, PAGESIZE) == -1) { _npassert(!close(fd)); return SEM_FAILED; } - - sem = mmap(0, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + sem = mmap(0, PAGESIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (sem != MAP_FAILED) { + atomic_store_explicit(&sem->sem_value, value, memory_order_relaxed); + sem->sem_magic = SEM_MAGIC_NAMED; + sem->sem_dev = st.st_dev; + sem->sem_ino = st.st_ino; sem->sem_pshared = true; } else { sem = SEM_FAILED; } - _npassert(!close(fd)); return sem; } + +static struct Semaphore *sem_open_find(const char *path) { + struct Semaphore *s; + for (s = g_semaphores.list; s; s = s->next) { + if (!strcmp(path, s->path)) { + return s; + } + } + return 0; +} + +static struct Semaphore *sem_open_reopen(const char *path) { + int e = errno; + struct stat st; + struct Semaphore *s; + for (s = g_semaphores.list; s; s = s->next) { + if (!s->dead && // + !strcmp(path, s->path)) { + if (!fstatat(AT_FDCWD, path, &st, 0) && // + st.st_dev == s->sem->sem_dev && // + st.st_ino == s->sem->sem_ino) { + return s; + } else { + errno = e; + s->dead = true; + } + } + } + return 0; +} + +static struct Semaphore *sem_open_get(const sem_t *sem, + struct Semaphore ***out_prev) { + struct Semaphore *s, *t, **p; + for (p = &g_semaphores.list, s = *p; s; p = &s->next, s = s->next) { + if (s && sem == s->sem) { + *out_prev = p; + return s; + } + } + return 0; +} + +/** + * Initializes and opens named semaphore. + * + * This function tracks open semaphore objects within a process. When a + * process calls sem_open() multiple times with the same name, then the + * same shared memory address will be returned, unless it was unlinked. + * + * @param name is arbitrary string that begins with a slash character + * @param oflag can have any of: + * - `O_CREAT` to create the named semaphore if it doesn't exist, + * in which case two additional arguments must be supplied + * - `O_EXCL` to raise `EEXIST` if semaphore already exists + * @param mode is octal mode bits, required if `oflag & O_CREAT` + * @param value is initial of semaphore, required if `oflag & O_CREAT` + * @return semaphore object which needs sem_close(), or SEM_FAILED w/ errno + * @raise ENOSPC if file system is full when `name` would be `O_CREAT`ed + * @raise EINVAL if `oflag` has bits other than `O_CREAT | O_EXCL` + * @raise EINVAL if `value` is negative or exceeds `SEM_VALUE_MAX` + * @raise EEXIST if `O_CREAT|O_EXCL` is used and semaphore exists + * @raise EACCES if we didn't have permission to create semaphore + * @raise EACCES if recreating open semaphore pending an unlink + * @raise EMFILE if process `RLIMIT_NOFILE` has been reached + * @raise ENFILE if system-wide file limit has been reached + * @raise ENOMEM if we require more vespene gas + * @raise EINTR if signal handler was called + * @threadsafe + */ +sem_t *sem_open(const char *name, int oflag, ...) { + sem_t *sem; + va_list va; + struct Semaphore *s; + unsigned mode = 0, value = 0; + char *path, pathbuf[PATH_MAX]; + if (oflag & ~(O_CREAT | O_EXCL)) { + einval(); + return SEM_FAILED; + } + if (oflag & O_CREAT) { + va_start(va, oflag); + mode = va_arg(va, unsigned); + value = va_arg(va, unsigned); + va_end(va); + if (value > SEM_VALUE_MAX) { + einval(); + return SEM_FAILED; + } + } + if (!(path = sem_path_np(name, pathbuf, sizeof(pathbuf)))) { + return SEM_FAILED; + } + sem_open_init(); + sem_open_lock(); + if ((s = sem_open_reopen(path))) { + if (s->sem->sem_lazydelete) { + if (oflag & O_CREAT) { + eacces(); + } else { + enoent(); + } + sem = SEM_FAILED; + } else if (~oflag & O_EXCL) { + sem = s->sem; + atomic_fetch_add_explicit(&sem->sem_prefs, 1, memory_order_acquire); + ++s->refs; + } else { + eexist(); + sem = SEM_FAILED; + } + } else if ((s = calloc(1, sizeof(struct Semaphore)))) { + if ((s->path = strdup(path))) { + if ((sem = sem_open_impl(path, oflag, mode, value)) != SEM_FAILED) { + atomic_fetch_add_explicit(&sem->sem_prefs, 1, memory_order_relaxed); + s->next = g_semaphores.list; + s->sem = sem; + s->refs = 1; + g_semaphores.list = s; + } else { + free(s->path); + free(s); + } + } else { + free(s); + sem = SEM_FAILED; + } + } else { + sem = SEM_FAILED; + } + sem_open_unlock(); + return sem; +} + +/** + * Closes named semaphore. + * + * Calling sem_close() on a semaphore not created by sem_open() has + * undefined behavior. Using `sem` after calling sem_close() from either + * the current process or forked processes sharing the same address is + * also undefined behavior. If any threads in this process or forked + * children are currently blocked on `sem` then calling sem_close() has + * undefined behavior. + * + * @param sem was created with sem_open() + * @return 0 on success, or -1 w/ errno + */ +int sem_close(sem_t *sem) { + int rc, prefs; + bool unmap, delete; + struct Semaphore *s, **p; + _npassert(sem->sem_magic == SEM_MAGIC_NAMED); + sem_open_init(); + sem_open_lock(); + _npassert((s = sem_open_get(sem, &p))); + prefs = atomic_fetch_add_explicit(&sem->sem_prefs, -1, memory_order_release); + _npassert(s->refs > 0); + if ((unmap = !--s->refs)) { + _npassert(prefs > 0); + delete = sem->sem_lazydelete && prefs == 1; + *p = s->next; + } else { + _npassert(prefs > 1); + delete = false; + } + sem_open_unlock(); + if (unmap) { + _npassert(!munmap(sem, PAGESIZE)); + } + if (delete) { + rc = unlink(s->path); + } + if (unmap) { + free(s->path); + free(s); + } + return 0; +} + +/** + * Removes named semaphore. + * + * This causes the file resource to be deleted. If processes have this + * semaphore currently opened, then on platforms like Windows deletion + * may be postponed until the last process calls sem_close(). + * + * @param name can be absolute path or should be component w/o slashes + * @return 0 on success, or -1 w/ errno + * @raise ACCESS if Windows is being fussy about deleting open files + * @raise EPERM if pledge() is in play w/o `cpath` promise + * @raise ENOENT if named semaphore doesn't exist + * @raise EACCES if permission is denied + * @raise ENAMETOOLONG if too long + */ +int sem_unlink(const char *name) { + int rc, e = errno; + struct Semaphore *s; + char *path, pathbuf[PATH_MAX]; + if (!(path = sem_path_np(name, pathbuf, sizeof(pathbuf)))) return -1; + if ((rc = unlink(path)) == -1 && IsWindows() && errno == EACCES) { + sem_open_init(); + sem_open_lock(); + if ((s = sem_open_find(path))) { + s->sem->sem_lazydelete = true; + errno = e; + rc = 0; + } + sem_open_unlock(); + } + return rc; +} diff --git a/libc/thread/sem_path_np.c b/libc/thread/sem_path_np.c new file mode 100644 index 000000000..3d2739903 --- /dev/null +++ b/libc/thread/sem_path_np.c @@ -0,0 +1,53 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2022 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/dce.h" +#include "libc/str/blake2.h" +#include "libc/str/path.h" +#include "libc/str/str.h" +#include "libc/sysv/errfuns.h" +#include "libc/thread/thread.h" + +/** + * Returns filesystem pathname of named semaphore. + * + * @param name is `name` of semaphore which should begin with slash + * @param buf is temporary storage with at least `size` bytes + * @param size is size of `buf` in bytes + * @return pointer to file system path + * @raise ENAMETOOLONG if constructed path would exceed `size` + */ +const char *sem_path_np(const char *name, char *buf, size_t size) { + char *p; + unsigned n; + const char *path, *a; + uint8_t digest[BLAKE2B256_DIGEST_LENGTH]; + a = "/tmp/", n = 5; + if (IsLinux()) a = "/dev/shm/", n = 9; + if (n + BLAKE2B256_DIGEST_LENGTH * 2 + 4 < size) { + BLAKE2B256(name, strlen(name), digest); + p = mempcpy(buf, a, n); + p = hexpcpy(p, digest, BLAKE2B256_DIGEST_LENGTH); + p = mempcpy(p, ".sem", 5); + path = buf; + } else { + enametoolong(); + path = 0; + } + return path; +} diff --git a/libc/thread/sem_post.c b/libc/thread/sem_post.c index b20cc0a91..91532a69d 100644 --- a/libc/thread/sem_post.c +++ b/libc/thread/sem_post.c @@ -17,9 +17,10 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/calls/calls.h" #include "libc/errno.h" #include "libc/intrin/atomic.h" -#include "libc/intrin/strace.internal.h" +#include "libc/limits.h" #include "libc/sysv/errfuns.h" #include "libc/thread/semaphore.h" #include "third_party/nsync/futex.internal.h" @@ -31,14 +32,17 @@ * @raise EINVAL if `sem` isn't valid */ int sem_post(sem_t *sem) { - int rc; - int old = atomic_fetch_add_explicit(&sem->sem_value, 1, memory_order_relaxed); + int rc, old, wakeups; + _unassert(sem->sem_pshared || sem->sem_pid == getpid()); + old = atomic_fetch_add_explicit(&sem->sem_value, 1, memory_order_relaxed); + _unassert(old > INT_MIN); if (old >= 0) { - _npassert(nsync_futex_wake_(&sem->sem_value, 1, sem->sem_pshared) >= 0); + wakeups = nsync_futex_wake_(&sem->sem_value, 1, sem->sem_pshared); + _npassert(wakeups >= 0); rc = 0; } else { + wakeups = 0; rc = einval(); } - STRACE("sem_post(%p) → %d% m", sem, rc); return rc; } diff --git a/libc/thread/sem_timedwait.c b/libc/thread/sem_timedwait.c index 6f5d2c5eb..bdcb9a46a 100644 --- a/libc/thread/sem_timedwait.c +++ b/libc/thread/sem_timedwait.c @@ -21,10 +21,9 @@ #include "libc/calls/struct/timespec.internal.h" #include "libc/errno.h" #include "libc/intrin/atomic.h" -#include "libc/intrin/strace.internal.h" +#include "libc/limits.h" #include "libc/sysv/errfuns.h" #include "libc/thread/semaphore.h" -#include "libc/thread/semaphore.internal.h" #include "third_party/nsync/futex.internal.h" static void sem_delay(int n) { @@ -79,6 +78,9 @@ int sem_timedwait(sem_t *sem, const struct timespec *abstime) { } } + _unassert(atomic_fetch_add_explicit(&sem->sem_waiters, +1, + memory_order_acquire) >= 0); + do { if (!(v = atomic_load_explicit(&sem->sem_value, memory_order_relaxed))) { rc = nsync_futex_wait_(&sem->sem_value, v, sem->sem_pshared, @@ -101,13 +103,15 @@ int sem_timedwait(sem_t *sem, const struct timespec *abstime) { } else if (v > 0) { rc = 0; } else { + _unassert(v > INT_MIN); rc = einval(); } } while (!rc && (!v || !atomic_compare_exchange_weak_explicit( &sem->sem_value, &v, v - 1, memory_order_acquire, memory_order_relaxed))); - STRACE("sem_timedwait(%p, %s) → %d% m", sem, DescribeTimespec(0, abstime), - rc); + _unassert(atomic_fetch_add_explicit(&sem->sem_waiters, -1, + memory_order_release) > 0); + return rc; } diff --git a/libc/thread/sem_trywait.c b/libc/thread/sem_trywait.c index c4bf6d83c..765a4187a 100644 --- a/libc/thread/sem_trywait.c +++ b/libc/thread/sem_trywait.c @@ -17,8 +17,10 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/calls/calls.h" #include "libc/errno.h" #include "libc/intrin/atomic.h" +#include "libc/limits.h" #include "libc/sysv/errfuns.h" #include "libc/thread/semaphore.h" @@ -32,8 +34,10 @@ */ int sem_trywait(sem_t *sem) { int v; + _unassert(sem->sem_pshared || sem->sem_pid == getpid()); v = atomic_load_explicit(&sem->sem_value, memory_order_relaxed); do { + _unassert(v > INT_MIN); if (!v) return eagain(); if (v < 0) return einval(); } while (!atomic_compare_exchange_weak_explicit( diff --git a/libc/thread/semaphore.h b/libc/thread/semaphore.h index b9b2adc74..052a2dee6 100644 --- a/libc/thread/semaphore.h +++ b/libc/thread/semaphore.h @@ -4,12 +4,21 @@ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ -#define SEM_FAILED ((sem_t *)0) +#define SEM_FAILED ((sem_t *)0) +#define SEM_MAGIC_NAMED 0xDEADBEEFu +#define SEM_MAGIC_UNNAMED 0xFEEDABEEu typedef struct { union { struct { _Atomic(int) sem_value; + _Atomic(int) sem_waiters; + _Atomic(int) sem_prefs; /* named only */ + unsigned sem_magic; + int64_t sem_dev; /* named only */ + int64_t sem_ino; /* named only */ + int sem_pid; /* unnamed only */ + bool sem_lazydelete; /* named only */ bool sem_pshared; }; void *sem_space[32]; @@ -26,6 +35,7 @@ int sem_getvalue(sem_t *, int *); sem_t *sem_open(const char *, int, ...); int sem_close(sem_t *); int sem_unlink(const char *); +const char *sem_path_np(const char *, char *, size_t); COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/thread/semaphore.internal.h b/libc/thread/semaphore.internal.h deleted file mode 100644 index b91a57cb0..000000000 --- a/libc/thread/semaphore.internal.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_THREAD_SEMAPHORE_INTERNAL_H_ -#define COSMOPOLITAN_LIBC_THREAD_SEMAPHORE_INTERNAL_H_ -#if !(__ASSEMBLER__ + __LINKER__ + 0) -COSMOPOLITAN_C_START_ - -const char *__sem_name(const char *, char[hasatleast PATH_MAX]) hidden; - -COSMOPOLITAN_C_END_ -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ -#endif /* COSMOPOLITAN_LIBC_THREAD_SEMAPHORE_INTERNAL_H_ */ diff --git a/libc/thread/thread.h b/libc/thread/thread.h index 41065ef32..6f2d5eda9 100644 --- a/libc/thread/thread.h +++ b/libc/thread/thread.h @@ -63,7 +63,7 @@ typedef struct pthread_mutex_s { unsigned _pshared : 1; unsigned _depth : 8; unsigned _owner : 21; - void *_nsync; + long _pid; } pthread_mutex_t; typedef struct pthread_mutexattr_s { @@ -149,6 +149,7 @@ int pthread_mutexattr_gettype(const pthread_mutexattr_t *, int *); int pthread_mutexattr_settype(pthread_mutexattr_t *, int); int pthread_mutexattr_setpshared(pthread_mutexattr_t *, int); int pthread_mutexattr_getpshared(const pthread_mutexattr_t *, int *); +int pthread_atfork(void (*)(void), void (*)(void), void (*)(void)); int pthread_mutex_init(pthread_mutex_t *, const pthread_mutexattr_t *); int pthread_mutex_lock(pthread_mutex_t *); int pthread_mutex_unlock(pthread_mutex_t *); diff --git a/libc/thread/tls.h b/libc/thread/tls.h index c8457f694..4dbcb03a1 100644 --- a/libc/thread/tls.h +++ b/libc/thread/tls.h @@ -4,6 +4,8 @@ #define TLS_ALIGNMENT 64 #define TIB_FLAG_TIME_CRITICAL 1 +#define TIB_FLAG_VFORKED 2 +#define TIB_FLAG_WINCRASHING 4 #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ @@ -24,8 +26,8 @@ struct CosmoTib { struct CosmoTib *tib_self2; /* 0x30 */ _Atomic(int32_t) tib_tid; /* 0x38 */ int32_t tib_errno; /* 0x3c */ + uint64_t tib_flags; /* 0x40 */ void *tib_nsync; - uint64_t tib_flags; uint64_t tib_sigmask; void *tib_reserved3; void *tib_reserved4; diff --git a/libc/time/localtime.c b/libc/time/localtime.c index df06ba81a..b20a3ed93 100644 --- a/libc/time/localtime.c +++ b/libc/time/localtime.c @@ -5,12 +5,12 @@ #include "libc/calls/calls.h" #include "libc/intrin/bits.h" #include "libc/intrin/nopl.internal.h" -#include "libc/thread/thread.h" -#include "libc/mem/mem.h" -#include "libc/thread/tls.h" #include "libc/mem/gc.h" +#include "libc/mem/mem.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/struct/tm.h" #include "libc/time/time.h" #include "libc/time/tz.internal.h" @@ -47,21 +47,28 @@ STATIC_YOINK("usr/share/zoneinfo/UTC"); static pthread_mutex_t locallock; -int localtime_lock(void) { +void localtime_lock(void) { pthread_mutex_lock(&locallock); - return 0; } void localtime_unlock(void) { pthread_mutex_unlock(&locallock); } +void localtime_funlock(void) { + pthread_mutex_init(&locallock, 0); +} + +__attribute__((__constructor__)) static void localtime_init(void) { + localtime_funlock(); + pthread_atfork(localtime_lock, + localtime_unlock, + localtime_funlock); +} + #ifdef _NOPL0 #define localtime_lock() _NOPL0("__threadcalls", localtime_lock) #define localtime_unlock() _NOPL0("__threadcalls", localtime_unlock) -#else -#define localtime_lock() (__threaded ? localtime_lock() : 0) -#define localtime_unlock() (__threaded ? localtime_unlock() : 0) #endif #ifndef TZ_ABBR_MAX_LEN diff --git a/libc/zipos/lock.c b/libc/zipos/lock.c index 464619308..f26012da9 100644 --- a/libc/zipos/lock.c +++ b/libc/zipos/lock.c @@ -28,3 +28,12 @@ void(__zipos_lock)(void) { void(__zipos_unlock)(void) { pthread_mutex_unlock(&__zipos_lock_obj); } + +void __zipos_funlock(void) { + pthread_mutex_init(&__zipos_lock_obj, 0); +} + +__attribute__((__constructor__)) static void __zipos_init(void) { + __zipos_funlock(); + pthread_atfork(__zipos_lock, __zipos_unlock, __zipos_funlock); +} diff --git a/test/libc/calls/raise_race_test.c b/test/libc/calls/raise_race_test.c new file mode 100644 index 000000000..57a04836f --- /dev/null +++ b/test/libc/calls/raise_race_test.c @@ -0,0 +1,117 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ │ +│ libc-test │ +│ Copyright © 2005-2013 libc-test AUTHORS │ +│ │ +│ Permission is hereby granted, free of charge, to any person obtaining │ +│ a copy of this software and associated documentation files (the │ +│ "Software"), to deal in the Software without restriction, including │ +│ without limitation the rights to use, copy, modify, merge, publish, │ +│ distribute, sublicense, and/or sell copies of the Software, and to │ +│ permit persons to whom the Software is furnished to do so, subject to │ +│ the following conditions: │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, │ +│ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF │ +│ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. │ +│ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY │ +│ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, │ +│ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE │ +│ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │ +│ │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/atomic.h" +#include "libc/calls/calls.h" +#include "libc/calls/struct/sigaction.h" +#include "libc/dce.h" +#include "libc/errno.h" +#include "libc/intrin/kprintf.h" +#include "libc/runtime/runtime.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/sig.h" +#include "libc/testlib/testlib.h" +#include "libc/thread/thread.h" + +// libc-test/src/regression/raise-race.c +// commit: 370f78f2c80c64b7b0780a01e672494a26b5678e 2011-03-09 +// commit: 0bed7e0acfd34e3fb63ca0e4d99b7592571355a9 2011-03-09 +// raise should be robust against async fork in a signal handler +// [jart] i can't believe fork() is async-signal-safe + +#define t_error(...) \ + do { \ + kprintf(__VA_ARGS__); \ + ++t_status; \ + } while (0) + +static atomic_int c0; +static atomic_int c1; +static atomic_int child; +static atomic_int t_status; + +static void handler0(int sig) { + c0++; +} + +static void handler1(int sig) { + c1++; + switch (fork()) { + case 0: + child = 1; + break; + case -1: + t_error("fork failed: %s\n", strerror(errno)); + default: + break; + } +} + +static void *start(void *arg) { + int i, r, s; + for (i = 0; i < 1000; i++) { + r = raise(SIGRTMIN); + if (r) t_error("raise failed: %s\n", strerror(errno)); + } + if (c0 != 1000) { + t_error("lost signals: got %d, wanted 1000 (ischild %d forks %d)\n", c0, + child, c1); + } + if (child) _exit(t_status); + /* make sure we got all pthread_kills, then wait the forked children */ + while (c1 < 100) donothing; + for (i = 0; i < 100; i++) { + r = wait(&s); + if (r == -1) { + t_error("wait failed: %s\n", strerror(errno)); + } else if (!WIFEXITED(s) || WTERMSIG(s)) { + t_error("child failed: pid:%d status:%d sig:%s\n", r, s, + strsignal(WTERMSIG(s))); + } + } + return 0; +} + +TEST(raise, test) { + if (IsNetbsd()) return; // why doesn't it work? + if (IsOpenbsd()) return; // no support for realtime signals yet + if (IsXnu()) return; // no support for realtime signals yet + if (IsWindows()) return; // TODO(jart): why does it exit 128+SIGRTMIN? + void *p; + int r, i, s; + pthread_t t; + if (signal(SIGRTMIN, handler0) == SIG_ERR) + t_error("registering signal handler failed: %s\n", strerror(errno)); + if (signal(SIGRTMIN + 1, handler1) == SIG_ERR) + t_error("registering signal handler failed: %s\n", strerror(errno)); + r = pthread_create(&t, 0, start, 0); + if (r) t_error("pthread_create failed: %s\n", strerror(r)); + for (i = 0; i < 100; i++) { + r = pthread_kill(t, SIGRTMIN + 1); + if (r) t_error("phread_kill failed: %s\n", strerror(r)); + } + r = pthread_join(t, &p); + if (r) t_error("pthread_join failed: %s\n", strerror(r)); + ASSERT_EQ(0, t_status); +} diff --git a/test/libc/intrin/describesigset_test.c b/test/libc/intrin/describesigset_test.c index e59b6f576..47f1e377c 100644 --- a/test/libc/intrin/describesigset_test.c +++ b/test/libc/intrin/describesigset_test.c @@ -22,6 +22,12 @@ #include "libc/sysv/consts/sig.h" #include "libc/testlib/testlib.h" +TEST(DescribeSigset, full) { + sigset_t ss; + sigfillset(&ss); + EXPECT_STREQ("~{}", DescribeSigset(0, &ss)); +} + TEST(DescribeSigset, present) { sigset_t ss; sigemptyset(&ss); diff --git a/test/libc/intrin/lock_test.c b/test/libc/intrin/lock_test.c index 8496161f3..3df9b7475 100644 --- a/test/libc/intrin/lock_test.c +++ b/test/libc/intrin/lock_test.c @@ -203,7 +203,6 @@ int main(int argc, char *argv[]) { ASSERT_EQ(0, pthread_mutex_lock(&mu)); ASSERT_EQ(EDEADLK, pthread_mutex_lock(&mu)); ASSERT_EQ(0, pthread_mutex_unlock(&mu)); - ASSERT_EQ(EPERM, pthread_mutex_unlock(&mu)); ASSERT_EQ(0, pthread_mutex_destroy(&mu)); ASSERT_EQ(1, __tls_enabled); diff --git a/test/libc/intrin/pthread_atfork_test.c b/test/libc/intrin/pthread_atfork_test.c new file mode 100644 index 000000000..d15292c5e --- /dev/null +++ b/test/libc/intrin/pthread_atfork_test.c @@ -0,0 +1,114 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2022 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/dce.h" +#include "libc/intrin/intrin.h" +#include "libc/mem/gc.h" +#include "libc/mem/mem.h" +#include "libc/runtime/internal.h" +#include "libc/stdio/lock.internal.h" +#include "libc/testlib/subprocess.h" +#include "libc/testlib/testlib.h" +#include "libc/thread/thread.h" +#include "third_party/nsync/mu.h" + +int Ai; +const char *A[16]; + +// clang-format off +void prepare1(void) { A[Ai++] = "prepare1"; } +void prepare2(void) { A[Ai++] = "prepare2"; } +void parent1(void) { A[Ai++] = "parent1"; } +void parent2(void) { A[Ai++] = "parent2"; } +void child1(void) { A[Ai++] = "child1"; } +void child2(void) { A[Ai++] = "child2"; } +// clang-format on + +void *ForceThreadingMode(void *arg) { + return 0; +} + +TEST(pthread_atfork, test) { + int pid; + __enable_threads(); + 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]); + ASSERT_STREQ("prepare1", A[1]); + ASSERT_STREQ("child1", A[2]); + 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]); + ASSERT_STREQ("parent2", A[3]); + EXITS(0); +} + +pthread_mutex_t mu; + +void mu_lock(void) { + pthread_mutex_lock(&mu); +} + +void mu_unlock(void) { + pthread_mutex_unlock(&mu); +} + +void mu_funlock(void) { + pthread_mutex_init(&mu, 0); +} + +void *Worker(void *arg) { + for (int i = 0; i < 20; ++i) { + mu_lock(); + usleep(20); + mu_unlock(); + SPAWN(fork); + mu_lock(); + usleep(1); + mu_unlock(); + mu_lock(); + mu_unlock(); + EXITS(0); + mu_lock(); + mu_unlock(); + } + return 0; +} + +TEST(pthread_atfork, torture) { + if (IsWindows()) return; // TODO(jart): why do we get EBADF? it worked before + pthread_mutex_init(&mu, 0); + pthread_atfork(mu_lock, mu_unlock, mu_funlock); + int i, n = 4; + pthread_t *t = _gc(malloc(sizeof(pthread_t) * n)); + for (i = 0; i < n; ++i) { + ASSERT_EQ(0, pthread_create(t + i, 0, Worker, 0)); + } + for (i = 0; i < n; ++i) { + ASSERT_EQ(0, pthread_join(t[i], 0)); + } +} diff --git a/test/libc/intrin/pthread_mutex_lock_test.c b/test/libc/intrin/pthread_mutex_lock_test.c index cad17e189..5ce92d1b7 100644 --- a/test/libc/intrin/pthread_mutex_lock_test.c +++ b/test/libc/intrin/pthread_mutex_lock_test.c @@ -57,6 +57,14 @@ void SetUpOnce(void) { ASSERT_SYS(0, 0, pledge("stdio rpath", 0)); } +TEST(pthread_mutex_lock, initializer) { + struct sqlite3_mutex { + pthread_mutex_t mutex; + } mu[] = {{ + PTHREAD_MUTEX_INITIALIZER, + }}; +} + TEST(pthread_mutex_lock, normal) { pthread_mutex_t lock; pthread_mutexattr_t attr; @@ -100,12 +108,10 @@ TEST(pthread_mutex_lock, errorcheck) { 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(EPERM, pthread_mutex_unlock(&lock)); ASSERT_EQ(0, pthread_mutex_lock(&lock)); ASSERT_EQ(EDEADLK, pthread_mutex_lock(&lock)); ASSERT_EQ(EBUSY, pthread_mutex_trylock(&lock)); ASSERT_EQ(0, pthread_mutex_unlock(&lock)); - ASSERT_EQ(EPERM, pthread_mutex_unlock(&lock)); ASSERT_EQ(0, pthread_mutex_destroy(&lock)); } diff --git a/test/libc/stdio/popen_test.c b/test/libc/stdio/popen_test.c index 01785a5ad..7de492dff 100644 --- a/test/libc/stdio/popen_test.c +++ b/test/libc/stdio/popen_test.c @@ -132,13 +132,7 @@ TEST(popen, torture) { int i, n = 8; pthread_t *t = _gc(malloc(sizeof(pthread_t) * n)); testlib_extract("/zip/echo.com", "echo.com", 0755); - for (i = 0; i < n; ++i) { - ASSERT_EQ(0, pthread_create(t + i, 0, Worker, 0)); - } - for (i = 0; i < n; ++i) { - ASSERT_EQ(0, pthread_join(t[i], 0)); - } - for (i = 3; i < 16; ++i) { - ASSERT_SYS(EBADF, -1, fcntl(3, F_GETFL)); - } + for (i = 0; i < n; ++i) ASSERT_EQ(0, pthread_create(t + i, 0, Worker, 0)); + for (i = 0; i < n; ++i) ASSERT_EQ(0, pthread_join(t[i], 0)); + for (i = 3; i < 16; ++i) ASSERT_SYS(EBADF, -1, fcntl(i, F_GETFL)); } diff --git a/test/libc/stdio/spawn_test.c b/test/libc/stdio/posix_spawn_test.c similarity index 68% rename from test/libc/stdio/spawn_test.c rename to test/libc/stdio/posix_spawn_test.c index 9ec4da203..d3401260d 100644 --- a/test/libc/stdio/spawn_test.c +++ b/test/libc/stdio/posix_spawn_test.c @@ -16,19 +16,26 @@ │ 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/state.internal.h" +#include "libc/calls/struct/sigaction.h" #include "libc/dce.h" #include "libc/fmt/conv.h" #include "libc/intrin/kprintf.h" #include "libc/intrin/safemacros.internal.h" +#include "libc/mem/gc.h" +#include "libc/mem/mem.h" #include "libc/runtime/internal.h" #include "libc/runtime/runtime.h" #include "libc/stdio/posix_spawn.h" #include "libc/stdio/stdio.h" #include "libc/sysv/consts/auxv.h" #include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/sig.h" #include "libc/testlib/ezbench.h" #include "libc/testlib/testlib.h" +#include "libc/thread/thread.h" char testlib_enable_tmp_setup_teardown; @@ -41,7 +48,7 @@ __attribute__((__constructor__)) static void init(void) { } } -TEST(spawn, test) { +TEST(posix_spawn, test) { int rc, ws, pid; char *prog = GetProgramExecutableName(); char *args[] = {program_invocation_name, NULL}; @@ -52,7 +59,7 @@ TEST(spawn, test) { EXPECT_EQ(42, WEXITSTATUS(ws)); } -TEST(spawn, pipe) { +TEST(posix_spawn, pipe) { char buf[10]; int p[2], pid, status; const char *pn = "./echo.com"; @@ -71,6 +78,76 @@ TEST(spawn, pipe) { ASSERT_EQ(0, posix_spawn_file_actions_destroy(&fa)); } +_Thread_local atomic_int gotsome; + +void OhMyGoth(int sig) { + ++gotsome; +} + +// time for a vfork() clone() signal bloodbath +TEST(posix_spawn, torture) { + int n = 10; + int ws, pid; + short flags; + sigset_t allsig; + posix_spawnattr_t attr; + posix_spawn_file_actions_t fa; + signal(SIGUSR2, OhMyGoth); + sigfillset(&allsig); + if (!fileexists("echo.com")) { + testlib_extract("/zip/echo.com", "echo.com", 0755); + } + // XXX: NetBSD doesn't seem to let us set the scheduler to itself ;_; + ASSERT_EQ(0, posix_spawnattr_init(&attr)); + ASSERT_EQ(0, posix_spawnattr_setflags( + &attr, POSIX_SPAWN_RESETIDS | POSIX_SPAWN_SETSIGDEF | + POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETPGROUP | + POSIX_SPAWN_SETSIGMASK | + (IsNetbsd() ? 0 : POSIX_SPAWN_SETSCHEDULER))); + ASSERT_EQ(0, posix_spawnattr_setsigmask(&attr, &allsig)); + ASSERT_EQ(0, posix_spawnattr_setsigdefault(&attr, &allsig)); + ASSERT_EQ(0, posix_spawn_file_actions_init(&fa)); + ASSERT_EQ(0, posix_spawn_file_actions_addclose(&fa, 0)); + ASSERT_EQ( + 0, posix_spawn_file_actions_addopen(&fa, 1, "/dev/null", O_WRONLY, 0644)); + ASSERT_EQ(0, posix_spawn_file_actions_adddup2(&fa, 1, 0)); + for (int i = 0; i < n; ++i) { + char *volatile zzz = malloc(13); + volatile int fd = open("/dev/null", O_WRONLY); + char *args[] = {"./echo.com", NULL}; + char *envs[] = {NULL}; + raise(SIGUSR2); + ASSERT_EQ(0, posix_spawn(&pid, "./echo.com", &fa, &attr, args, envs)); + ASSERT_FALSE(__vforked); + ASSERT_NE(-1, waitpid(pid, &ws, 0)); + ASSERT_TRUE(WIFEXITED(ws)); + ASSERT_EQ(0, WEXITSTATUS(ws)); + close(fd); + free(zzz); + } + // TODO(jart): Why does it deliver 2x the signals sometimes? + ASSERT_NE(0, gotsome); + ASSERT_EQ(0, posix_spawn_file_actions_destroy(&fa)); + ASSERT_EQ(0, posix_spawnattr_destroy(&attr)); +} + +void *Torturer(void *arg) { + posix_spawn_torture(); + return 0; +} + +TEST(posix_spawn, agony) { + int i, n = 3; + pthread_t *t = _gc(malloc(sizeof(pthread_t) * n)); + testlib_extract("/zip/echo.com", "echo.com", 0755); + for (i = 0; i < n; ++i) { + ASSERT_EQ(0, pthread_create(t + i, 0, Torturer, 0)); + } + for (i = 0; i < n; ++i) { + ASSERT_EQ(0, pthread_join(t[i], 0)); + } +} + /* * BEST LINUX FORK+EXEC+EXIT+WAIT PERFORMANCE * The fastest it can go with fork() is 40µs diff --git a/test/libc/stdio/test.mk b/test/libc/stdio/test.mk index 3e001f3ab..deb7c8434 100644 --- a/test/libc/stdio/test.mk +++ b/test/libc/stdio/test.mk @@ -6,99 +6,109 @@ PKGS += TEST_LIBC_STDIO TEST_LIBC_STDIO_SRCS := $(wildcard test/libc/stdio/*.c) TEST_LIBC_STDIO_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_STDIO_SRCS)) -TEST_LIBC_STDIO_OBJS = \ +TEST_LIBC_STDIO_OBJS = \ $(TEST_LIBC_STDIO_SRCS:%.c=o/$(MODE)/%.o) -TEST_LIBC_STDIO_COMS = \ +TEST_LIBC_STDIO_COMS = \ $(TEST_LIBC_STDIO_SRCS:%.c=o/$(MODE)/%.com) -TEST_LIBC_STDIO_BINS = \ - $(TEST_LIBC_STDIO_COMS) \ +TEST_LIBC_STDIO_BINS = \ + $(TEST_LIBC_STDIO_COMS) \ $(TEST_LIBC_STDIO_COMS:%=%.dbg) -TEST_LIBC_STDIO_TESTS = \ +TEST_LIBC_STDIO_TESTS = \ $(TEST_LIBC_STDIO_SRCS_TEST:%.c=o/$(MODE)/%.com.ok) -TEST_LIBC_STDIO_CHECKS = \ +TEST_LIBC_STDIO_CHECKS = \ $(TEST_LIBC_STDIO_SRCS_TEST:%.c=o/$(MODE)/%.com.runs) -TEST_LIBC_STDIO_DIRECTDEPS = \ - LIBC_CALLS \ - LIBC_FMT \ - LIBC_INTRIN \ - LIBC_MEM \ - LIBC_NEXGEN32E \ - LIBC_RUNTIME \ - LIBC_STDIO \ - LIBC_STR \ - LIBC_STUBS \ - LIBC_SYSV \ - LIBC_TINYMATH \ - LIBC_TESTLIB \ - LIBC_THREAD \ - LIBC_TIME \ - LIBC_LOG \ - LIBC_X \ - LIBC_ZIPOS \ - THIRD_PARTY_GDTOA \ - THIRD_PARTY_MBEDTLS \ - THIRD_PARTY_MUSL \ - THIRD_PARTY_ZLIB \ +TEST_LIBC_STDIO_DIRECTDEPS = \ + LIBC_CALLS \ + LIBC_FMT \ + LIBC_INTRIN \ + LIBC_MEM \ + LIBC_NEXGEN32E \ + LIBC_RUNTIME \ + LIBC_STDIO \ + LIBC_STR \ + LIBC_STUBS \ + LIBC_SYSV \ + LIBC_TINYMATH \ + LIBC_TESTLIB \ + LIBC_THREAD \ + LIBC_TIME \ + LIBC_LOG \ + LIBC_X \ + LIBC_ZIPOS \ + THIRD_PARTY_GDTOA \ + THIRD_PARTY_MBEDTLS \ + THIRD_PARTY_MUSL \ + THIRD_PARTY_ZLIB \ THIRD_PARTY_ZLIB_GZ -TEST_LIBC_STDIO_DEPS := \ +TEST_LIBC_STDIO_DEPS := \ $(call uniq,$(foreach x,$(TEST_LIBC_STDIO_DIRECTDEPS),$($(x)))) -o/$(MODE)/test/libc/stdio/stdio.pkg: \ - $(TEST_LIBC_STDIO_OBJS) \ +o/$(MODE)/test/libc/stdio/stdio.pkg: \ + $(TEST_LIBC_STDIO_OBJS) \ $(foreach x,$(TEST_LIBC_STDIO_DIRECTDEPS),$($(x)_A).pkg) -o/$(MODE)/test/libc/stdio/%.com.dbg: \ - $(TEST_LIBC_STDIO_DEPS) \ - o/$(MODE)/test/libc/stdio/%.o \ - o/$(MODE)/test/libc/stdio/stdio.pkg \ - o/$(MODE)/tool/build/echo.zip.o \ - $(LIBC_TESTMAIN) \ - $(CRT) \ +o/$(MODE)/test/libc/stdio/%.com.dbg: \ + $(TEST_LIBC_STDIO_DEPS) \ + o/$(MODE)/test/libc/stdio/%.o \ + o/$(MODE)/test/libc/stdio/stdio.pkg \ + o/$(MODE)/tool/build/echo.zip.o \ + $(LIBC_TESTMAIN) \ + $(CRT) \ $(APE_NO_MODIFY_SELF) @$(APELINK) -o/$(MODE)/test/libc/stdio/system_test.com.dbg: \ - $(TEST_LIBC_STDIO_DEPS) \ - o/$(MODE)/test/libc/stdio/system_test.o \ - o/$(MODE)/test/libc/stdio/stdio.pkg \ - o/$(MODE)/tool/build/echo.com.zip.o \ - o/$(MODE)/tool/build/cocmd.com.zip.o \ - $(LIBC_TESTMAIN) \ - $(CRT) \ +o/$(MODE)/test/libc/stdio/system_test.com.dbg: \ + $(TEST_LIBC_STDIO_DEPS) \ + o/$(MODE)/test/libc/stdio/system_test.o \ + o/$(MODE)/test/libc/stdio/stdio.pkg \ + o/$(MODE)/tool/build/echo.com.zip.o \ + o/$(MODE)/tool/build/cocmd.com.zip.o \ + $(LIBC_TESTMAIN) \ + $(CRT) \ $(APE_NO_MODIFY_SELF) @$(APELINK) -o/$(MODE)/test/libc/stdio/popen_test.com.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.com.zip.o \ - $(LIBC_TESTMAIN) \ - $(CRT) \ +o/$(MODE)/test/libc/stdio/popen_test.com.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.com.zip.o \ + $(LIBC_TESTMAIN) \ + $(CRT) \ $(APE_NO_MODIFY_SELF) @$(APELINK) -o/$(MODE)/test/libc/stdio/spawn_test.com.dbg: \ - $(TEST_LIBC_STDIO_DEPS) \ - o/$(MODE)/test/libc/stdio/spawn_test.o \ - o/$(MODE)/test/libc/stdio/stdio.pkg \ - o/$(MODE)/tool/build/echo.com.zip.o \ - $(LIBC_TESTMAIN) \ - $(CRT) \ +o/$(MODE)/test/libc/stdio/posix_spawn_test.com.dbg: \ + $(TEST_LIBC_STDIO_DEPS) \ + o/$(MODE)/test/libc/stdio/posix_spawn_test.o \ + o/$(MODE)/test/libc/stdio/stdio.pkg \ + o/$(MODE)/tool/build/echo.com.zip.o \ + $(LIBC_TESTMAIN) \ + $(CRT) \ $(APE_NO_MODIFY_SELF) @$(APELINK) -$(TEST_LIBC_STDIO_OBJS): private \ - DEFAULT_CCFLAGS += \ +o/$(MODE)/test/libc/stdio/wut_test.com.dbg: \ + $(TEST_LIBC_STDIO_DEPS) \ + o/$(MODE)/test/libc/stdio/wut_test.o \ + o/$(MODE)/test/libc/stdio/stdio.pkg \ + o/$(MODE)/tool/build/echo.com.zip.o \ + $(LIBC_TESTMAIN) \ + $(CRT) \ + $(APE_NO_MODIFY_SELF) + @$(APELINK) + +$(TEST_LIBC_STDIO_OBJS): private \ + DEFAULT_CCFLAGS += \ -fno-builtin .PHONY: o/$(MODE)/test/libc/stdio -o/$(MODE)/test/libc/stdio: \ - $(TEST_LIBC_STDIO_BINS) \ +o/$(MODE)/test/libc/stdio: \ + $(TEST_LIBC_STDIO_BINS) \ $(TEST_LIBC_STDIO_CHECKS) diff --git a/test/libc/stdio/wut_test.c b/test/libc/stdio/wut_test.c new file mode 100644 index 000000000..dc3abef65 --- /dev/null +++ b/test/libc/stdio/wut_test.c @@ -0,0 +1,64 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2022 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/calls/struct/sigset.h" +#include "libc/dce.h" +#include "libc/mem/mem.h" +#include "libc/stdio/posix_spawn.h" +#include "libc/stdio/stdio.h" +#include "libc/sysv/consts/o.h" +#include "libc/testlib/testlib.h" + +char testlib_enable_tmp_setup_teardown; + +TEST(posix_spawn, torture) { + int ws, pid; + short flags; + sigset_t allsig; + posix_spawnattr_t attr; + posix_spawn_file_actions_t fa; + sigfillset(&allsig); + testlib_extract("/zip/echo.com", "echo.com", 0755); + // XXX: NetBSD doesn't seem to let us set the scheduler to itself ;_; + ASSERT_EQ(0, posix_spawnattr_init(&attr)); + ASSERT_EQ(0, posix_spawnattr_setflags( + &attr, POSIX_SPAWN_RESETIDS | POSIX_SPAWN_SETSIGDEF | + POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETPGROUP | + POSIX_SPAWN_SETSIGMASK | + (IsNetbsd() ? 0 : POSIX_SPAWN_SETSCHEDULER))); + ASSERT_EQ(0, posix_spawnattr_setsigmask(&attr, &allsig)); + ASSERT_EQ(0, posix_spawnattr_setsigdefault(&attr, &allsig)); + ASSERT_EQ(0, posix_spawn_file_actions_init(&fa)); + ASSERT_EQ(0, posix_spawn_file_actions_addclose(&fa, 0)); + ASSERT_EQ( + 0, posix_spawn_file_actions_addopen(&fa, 1, "/dev/null", O_WRONLY, 0644)); + ASSERT_EQ(0, posix_spawn_file_actions_adddup2(&fa, 1, 0)); + for (int i = 0; i < 15; ++i) { + char *volatile zzz = malloc(13); + char *args[] = {"./echo.com", NULL}; + char *envs[] = {NULL}; + ASSERT_EQ(0, posix_spawn(&pid, "./echo.com", &fa, &attr, args, envs)); + ASSERT_NE(-1, waitpid(pid, &ws, 0)); + ASSERT_TRUE(WIFEXITED(ws)); + ASSERT_EQ(0, WEXITSTATUS(ws)); + free(zzz); + } + ASSERT_EQ(0, posix_spawn_file_actions_destroy(&fa)); + ASSERT_EQ(0, posix_spawnattr_destroy(&attr)); +} diff --git a/libc/log/malloc_stats.c b/test/libc/str/hexpcpy_test.c similarity index 73% rename from libc/log/malloc_stats.c rename to test/libc/str/hexpcpy_test.c index 4a5612e99..3f1f89656 100644 --- a/libc/log/malloc_stats.c +++ b/test/libc/str/hexpcpy_test.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ Copyright 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,9 +16,23 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/mem/mem.h" -#include "third_party/dlmalloc/dlmalloc.h" +#include "libc/macros.internal.h" +#include "libc/stdio/rand.h" +#include "libc/str/str.h" +#include "libc/testlib/testlib.h" -void malloc_stats(void) { - dlmalloc_stats(); +TEST(hexpcpy, test) { + char buf[] = {0x00, 0x02, 0x20, 0x80, 0xf5, 0xff}; + char str[ARRAYLEN(buf) * 2 + 1]; + rngset(str, sizeof(str), _rand64, -1); + EXPECT_EQ(str + ARRAYLEN(buf) * 2, hexpcpy(str, buf, ARRAYLEN(buf))); + EXPECT_STREQ("00022080f5ff", str); +} + +TEST(hexpcpy, emptyBuf_writesNulTerminator) { + char buf[1]; + char str[1]; + rngset(str, sizeof(str), _rand64, -1); + EXPECT_EQ(str, hexpcpy(str, buf, 0)); + EXPECT_STREQ("", str); } diff --git a/test/libc/thread/pthread_create_test.c b/test/libc/thread/pthread_create_test.c index 20a26d229..62363ce63 100644 --- a/test/libc/thread/pthread_create_test.c +++ b/test/libc/thread/pthread_create_test.c @@ -86,20 +86,6 @@ TEST(pthread_create, testCreateExitJoin) { ASSERT_EQ((void *)3, rc); } -TEST(pthread_detach, testCreateReturn) { - pthread_t id; - ASSERT_EQ(0, pthread_create(&id, 0, Increment, 0)); - ASSERT_EQ(0, pthread_detach(id)); -} - -TEST(pthread_detach, testDetachUponCreation) { - pthread_attr_t attr; - ASSERT_EQ(0, pthread_attr_init(&attr)); - ASSERT_EQ(0, pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)); - ASSERT_EQ(0, pthread_create(0, &attr, Increment, 0)); - ASSERT_EQ(0, pthread_attr_destroy(&attr)); -} - static void *CheckSchedule(void *arg) { int rc, policy; struct sched_param prio; @@ -114,7 +100,7 @@ static void *CheckSchedule(void *arg) { return 0; } -TEST(pthread_detach, scheduling) { +TEST(pthread_create, scheduling) { pthread_t id; pthread_attr_t attr; struct sched_param pri = {sched_get_priority_min(SCHED_OTHER)}; @@ -134,7 +120,7 @@ static void *CheckStack(void *arg) { return 0; } -TEST(pthread_detach, testBigStack) { +TEST(pthread_create, testBigStack) { pthread_t id; pthread_attr_t attr; ASSERT_EQ(0, pthread_attr_init(&attr)); @@ -151,7 +137,7 @@ static void *CheckStack2(void *arg) { return 0; } -TEST(pthread_detach, testBiggerGuardSize) { +TEST(pthread_create, testBiggerGuardSize) { pthread_t id; pthread_attr_t attr; ASSERT_EQ(0, pthread_attr_init(&attr)); @@ -162,7 +148,7 @@ TEST(pthread_detach, testBiggerGuardSize) { ASSERT_EQ(0, pthread_join(id, 0)); } -TEST(pthread_detach, testCustomStack_withReallySmallSize) { +TEST(pthread_create, testCustomStack_withReallySmallSize) { char *stk; size_t siz; pthread_t id; diff --git a/test/libc/str/sigset_test.c b/test/libc/thread/pthread_detach_test.c similarity index 61% rename from test/libc/str/sigset_test.c rename to test/libc/thread/pthread_detach_test.c index 7dd2f1f50..6e37d381f 100644 --- a/test/libc/str/sigset_test.c +++ b/test/libc/thread/pthread_detach_test.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ Copyright 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,47 +16,46 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/bits.h" -#include "libc/intrin/safemacros.internal.h" #include "libc/calls/calls.h" -#include "libc/calls/struct/sigset.h" -#include "libc/str/str.h" +#include "libc/calls/struct/sigaction.h" +#include "libc/errno.h" +#include "libc/sysv/consts/sa.h" +#include "libc/sysv/consts/sig.h" #include "libc/testlib/testlib.h" +#include "libc/thread/thread.h" -sigset_t ss; - -TEST(sigemptyset, test) { - EXPECT_EQ(0, sigemptyset(&ss)); - EXPECT_BINEQ(u"        ", &ss); +void OnUsr1(int sig, struct siginfo *si, void *vctx) { + struct ucontext *ctx = vctx; } -TEST(sigfillset, test) { - EXPECT_EQ(0, sigfillset(&ss)); - EXPECT_BINEQ(u"λλλλλλλλ", &ss); +void SetUp(void) { + struct sigaction sig = {.sa_sigaction = OnUsr1, .sa_flags = SA_SIGINFO}; + sigaction(SIGUSR1, &sig, 0); } -TEST(sigaddset, test) { - sigemptyset(&ss); - EXPECT_EQ(0, sigaddset(&ss, 1)); - EXPECT_BINEQ(u"☺       ", &ss); - EXPECT_EQ(0, sigaddset(&ss, 64)); - EXPECT_BINEQ(u"☺      Ç", &ss); +void TriggerSignal(void) { + sched_yield(); + raise(SIGUSR1); + sched_yield(); } -TEST(sigdelset, test) { - sigfillset(&ss); - EXPECT_EQ(0, sigdelset(&ss, 1)); - EXPECT_BINEQ(u"■λλλλλλλ", &ss); - EXPECT_EQ(0, sigdelset(&ss, 64)); - EXPECT_BINEQ(u"■λλλλλλ⌂", &ss); +static void *Increment(void *arg) { + ASSERT_EQ(EDEADLK, pthread_join(pthread_self(), 0)); + ASSERT_EQ(gettid(), pthread_getthreadid_np()); + TriggerSignal(); + return (void *)((uintptr_t)arg + 1); } -TEST(sigismember, test) { - sigfillset(&ss); - EXPECT_TRUE(sigismember(&ss, 1)); - sigdelset(&ss, 1); - EXPECT_FALSE(sigismember(&ss, 1)); - EXPECT_TRUE(sigismember(&ss, 64)); - sigdelset(&ss, 64); - EXPECT_FALSE(sigismember(&ss, 64)); +TEST(pthread_detach, testCreateReturn) { + pthread_t id; + ASSERT_EQ(0, pthread_create(&id, 0, Increment, 0)); + ASSERT_EQ(0, pthread_detach(id)); +} + +TEST(pthread_detach, testDetachUponCreation) { + pthread_attr_t attr; + ASSERT_EQ(0, pthread_attr_init(&attr)); + ASSERT_EQ(0, pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)); + ASSERT_EQ(0, pthread_create(0, &attr, Increment, 0)); + ASSERT_EQ(0, pthread_attr_destroy(&attr)); } diff --git a/test/libc/thread/sem_open_test.c b/test/libc/thread/sem_open_test.c index 7e77d226a..958fa55e0 100644 --- a/test/libc/thread/sem_open_test.c +++ b/test/libc/thread/sem_open_test.c @@ -18,12 +18,15 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/dce.h" #include "libc/errno.h" +#include "libc/intrin/kprintf.h" #include "libc/mem/gc.h" #include "libc/mem/mem.h" #include "libc/runtime/runtime.h" #include "libc/stdio/temp.h" +#include "libc/str/str.h" #include "libc/sysv/consts/clock.h" #include "libc/sysv/consts/o.h" +#include "libc/testlib/subprocess.h" #include "libc/testlib/testlib.h" #include "libc/thread/semaphore.h" #include "libc/thread/thread.h" @@ -31,50 +34,158 @@ pthread_barrier_t barrier; char testlib_enable_tmp_setup_teardown; +void SetUp(void) { + // TODO(jart): Fix shocking GitHub Actions error. + if (getenv("CI")) exit(0); + sem_unlink("/fooz"); + sem_unlink("/barz"); +} + +void IgnoreStderr(void) { + close(2); + open("/dev/null", O_WRONLY); +} + void *Worker(void *arg) { sem_t *a, *b; struct timespec ts; - ASSERT_NE(SEM_FAILED, (a = sem_open("fooz", O_CREAT, 0644))); - ASSERT_NE(SEM_FAILED, (b = sem_open("barz", O_CREAT, 0644))); + ASSERT_NE(SEM_FAILED, (a = sem_open("/fooz", 0))); + ASSERT_EQ((sem_t *)arg, a); + ASSERT_NE(SEM_FAILED, (b = sem_open("/barz", 0))); if (pthread_barrier_wait(&barrier) == PTHREAD_BARRIER_SERIAL_THREAD) { - if (!IsWindows()) { // :'( - ASSERT_SYS(0, 0, sem_unlink("fooz")); - ASSERT_SYS(0, 0, sem_unlink("barz")); - } + ASSERT_SYS(0, 0, sem_unlink("/fooz")); } ASSERT_SYS(0, 0, clock_gettime(CLOCK_REALTIME, &ts)); ts.tv_sec += 1; ASSERT_SYS(0, 0, sem_post(a)); ASSERT_SYS(0, 0, sem_timedwait(b, &ts)); ASSERT_SYS(0, 0, sem_close(b)); + ASSERT_TRUE(testlib_memoryexists(b)); ASSERT_SYS(0, 0, sem_close(a)); + ASSERT_TRUE(testlib_memoryexists(a)); return 0; } +// 1. multiple threads opening same name must yield same address +// 2. semaphore memory is freed, when all threads have closed it +// 3. semaphore may be unlinked before it's closed, from creator +// 4. semaphore may be unlinked before it's closed, from threads TEST(sem_open, test) { - if (IsLinux()) return; // TODO(jart): Fix shocking GitHub Actions error. sem_t *a, *b; int i, r, n = 4; pthread_t *t = _gc(malloc(sizeof(pthread_t) * n)); - sem_unlink("barz"); - sem_unlink("fooz"); + sem_unlink("/fooz"); + sem_unlink("/barz"); errno = 0; ASSERT_EQ(0, pthread_barrier_init(&barrier, 0, n)); - ASSERT_NE(SEM_FAILED, (a = sem_open("fooz", O_CREAT, 0644))); - ASSERT_NE(SEM_FAILED, (b = sem_open("barz", O_CREAT, 0644))); + ASSERT_NE(SEM_FAILED, (a = sem_open("/fooz", O_CREAT, 0644, 0))); + ASSERT_NE(SEM_FAILED, (b = sem_open("/barz", O_CREAT, 0644, 0))); ASSERT_SYS(0, 0, sem_getvalue(a, &r)); ASSERT_EQ(0, r); ASSERT_SYS(0, 0, sem_getvalue(b, &r)); ASSERT_EQ(0, r); - for (i = 0; i < n; ++i) ASSERT_EQ(0, pthread_create(t + i, 0, Worker, 0)); + for (i = 0; i < n; ++i) ASSERT_EQ(0, pthread_create(t + i, 0, Worker, a)); for (i = 0; i < n; ++i) ASSERT_SYS(0, 0, sem_wait(a)); ASSERT_SYS(0, 0, sem_getvalue(a, &r)); ASSERT_EQ(0, r); for (i = 0; i < n; ++i) ASSERT_SYS(0, 0, sem_post(b)); for (i = 0; i < n; ++i) ASSERT_EQ(0, pthread_join(t[i], 0)); + ASSERT_SYS(0, 0, sem_unlink("/barz")); ASSERT_SYS(0, 0, sem_getvalue(b, &r)); ASSERT_EQ(0, r); ASSERT_SYS(0, 0, sem_close(b)); + ASSERT_FALSE(testlib_memoryexists(b)); ASSERT_SYS(0, 0, sem_close(a)); + ASSERT_FALSE(testlib_memoryexists(a)); ASSERT_EQ(0, pthread_barrier_destroy(&barrier)); } + +TEST(sem_close, withUnnamedSemaphore_isUndefinedBehavior) { + if (!IsModeDbg()) return; + sem_t sem; + ASSERT_SYS(0, 0, sem_init(&sem, 1, 0)); + SPAWN(fork); + IgnoreStderr(); + sem_close(&sem); + EXITS(77); + ASSERT_SYS(0, 0, sem_destroy(&sem)); +} + +TEST(sem_destroy, withNamedSemaphore_isUndefinedBehavior) { + if (!IsModeDbg()) return; + sem_t *sem; + ASSERT_NE(SEM_FAILED, (sem = sem_open("/boop", O_CREAT, 0644, 0))); + SPAWN(fork); + IgnoreStderr(); + sem_destroy(sem); + EXITS(77); + ASSERT_SYS(0, 0, sem_unlink("/boop")); + ASSERT_SYS(0, 0, sem_close(sem)); +} + +TEST(sem_open, inheritAcrossFork) { + sem_t *a, *b; + struct timespec ts; + ASSERT_SYS(0, 0, clock_gettime(CLOCK_REALTIME, &ts)); + ts.tv_sec += 1; + errno = 0; + ASSERT_NE(SEM_FAILED, (a = sem_open("/fooz", O_CREAT, 0644, 0))); + ASSERT_SYS(0, 0, sem_unlink("/fooz")); + ASSERT_NE(SEM_FAILED, (b = sem_open("/barz", O_CREAT, 0644, 0))); + ASSERT_SYS(0, 0, sem_unlink("/barz")); + SPAWN(fork); + ASSERT_SYS(0, 0, sem_post(a)); + ASSERT_SYS(0, 0, sem_wait(b)); + PARENT(); + ASSERT_SYS(0, 0, sem_wait(a)); + ASSERT_SYS(0, 0, sem_post(b)); + WAIT(exit, 0); + ASSERT_SYS(0, 0, sem_close(b)); + ASSERT_FALSE(testlib_memoryexists(b)); + ASSERT_SYS(0, 0, sem_close(a)); + ASSERT_FALSE(testlib_memoryexists(a)); +} + +TEST(sem_open, openReadonlyAfterUnlink_enoent) { + sem_t *sem; + sem_unlink("/fooz"); + ASSERT_NE(SEM_FAILED, (sem = sem_open("/fooz", O_CREAT, 0644, 0))); + ASSERT_EQ(0, sem_unlink("/fooz")); + ASSERT_EQ(SEM_FAILED, sem_open("/fooz", O_RDONLY)); + ASSERT_EQ(ENOENT, errno); + ASSERT_EQ(0, sem_close(sem)); +} + +TEST(sem_open, openReadonlyAfterIndependentUnlinkAndRecreate_returnsNewOne) { + if (1) return; + sem_t *a, *b; + ASSERT_NE(SEM_FAILED, (a = sem_open("/fooz", O_CREAT, 0644, 0))); + SPAWN(fork); + ASSERT_EQ(0, sem_unlink("/fooz")); + ASSERT_NE(SEM_FAILED, (b = sem_open("/fooz", O_CREAT, 0644, 0))); + ASSERT_NE(a, b); + ASSERT_SYS(0, 0, sem_post(a)); + ASSERT_SYS(0, 0, sem_wait(b)); + ASSERT_EQ(0, sem_close(b)); + PARENT(); + ASSERT_SYS(0, 0, sem_wait(a)); + ASSERT_NE(SEM_FAILED, (b = sem_open("/fooz", O_RDONLY))); + ASSERT_NE(a, b); + ASSERT_SYS(0, 0, sem_post(b)); + ASSERT_EQ(0, sem_close(b)); + WAIT(exit, 0); + ASSERT_EQ(0, sem_close(a)); +} + +TEST(sem_close, openTwiceCloseOnce_stillMapped) { + if (1) return; + sem_t *a, *b; + char name[] = "smXXXXXX"; + ASSERT_NE(NULL, mktemp(name)); + ASSERT_NE(SEM_FAILED, (a = sem_open(name, O_CREAT | O_EXCL, 0600, 0))); + ASSERT_NE(SEM_FAILED, (b = sem_open(name, 0))); + ASSERT_SYS(0, 0, sem_unlink(name)); + ASSERT_SYS(0, 0, sem_close(a)); + ASSERT_SYS(0, 0, sem_post(a)); + ASSERT_SYS(0, 0, sem_close(b)); +} diff --git a/test/libc/thread/sem_timedwait_test.c b/test/libc/thread/sem_timedwait_test.c index e22b11a27..cc31de3da 100644 --- a/test/libc/thread/sem_timedwait_test.c +++ b/test/libc/thread/sem_timedwait_test.c @@ -25,11 +25,119 @@ #include "libc/mem/mem.h" #include "libc/runtime/runtime.h" #include "libc/sysv/consts/clock.h" +#include "libc/sysv/consts/o.h" #include "libc/sysv/consts/sig.h" +#include "libc/testlib/subprocess.h" #include "libc/testlib/testlib.h" #include "libc/thread/semaphore.h" #include "libc/thread/thread.h" +void IgnoreStderr(void) { + close(2); + open("/dev/null", O_WRONLY); +} + +TEST(sem_init, einval) { + sem_t sem; + ASSERT_SYS(EINVAL, -1, sem_init(&sem, 0, -1)); +} + +TEST(sem_post, accessInChildProcessWhenNotPshared_isUndefinedBehavior) { + if (!IsModeDbg()) return; + int val; + sem_t sem; + ASSERT_SYS(0, 0, sem_init(&sem, 0, 0)); + SPAWN(fork); + IgnoreStderr(); + sem_post(&sem); + EXITS(77); +} + +TEST(sem_getvalue, accessInChildProcessWhenNotPshared_isUndefinedBehavior) { + if (!IsModeDbg()) return; + int val; + sem_t sem; + ASSERT_SYS(0, 0, sem_init(&sem, 0, 0)); + ASSERT_SYS(0, 0, sem_getvalue(&sem, &val)); + SPAWN(fork); + IgnoreStderr(); + sem_getvalue(&sem, &val); + EXITS(77); + ASSERT_SYS(0, 0, sem_destroy(&sem)); +} + +TEST(sem_timedwait, accessInChildProcessWhenNotPshared_isUndefinedBehavior) { + if (!IsModeDbg()) return; + int val; + sem_t sem; + ASSERT_SYS(0, 0, sem_init(&sem, 0, 0)); + SPAWN(fork); + IgnoreStderr(); + sem_timedwait(&sem, 0); + EXITS(77); + ASSERT_SYS(0, 0, sem_destroy(&sem)); +} + +TEST(sem_trywait, accessInChildProcessWhenNotPshared_isUndefinedBehavior) { + if (!IsModeDbg()) return; + int val; + sem_t sem; + ASSERT_SYS(0, 0, sem_init(&sem, 0, 0)); + SPAWN(fork); + IgnoreStderr(); + sem_trywait(&sem); + EXITS(77); + ASSERT_SYS(0, 0, sem_destroy(&sem)); +} + +TEST(sem_post, afterDestroyed_isUndefinedBehavior) { + if (!IsModeDbg()) return; + int val; + sem_t sem; + SPAWN(fork); + ASSERT_SYS(0, 0, sem_init(&sem, 0, 0)); + ASSERT_SYS(0, 0, sem_destroy(&sem)); + IgnoreStderr(); + sem_post(&sem); + EXITS(77); +} + +TEST(sem_trywait, afterDestroyed_isUndefinedBehavior) { + if (!IsModeDbg()) return; + int val; + sem_t sem; + SPAWN(fork); + ASSERT_SYS(0, 0, sem_init(&sem, 0, 0)); + ASSERT_SYS(0, 0, sem_destroy(&sem)); + IgnoreStderr(); + sem_trywait(&sem); + EXITS(77); +} + +TEST(sem_wait, afterDestroyed_isUndefinedBehavior) { + if (!IsModeDbg()) return; + int val; + sem_t sem; + SPAWN(fork); + ASSERT_SYS(0, 0, sem_init(&sem, 0, 0)); + ASSERT_SYS(0, 0, sem_destroy(&sem)); + IgnoreStderr(); + sem_wait(&sem); + EXITS(77); +} + +TEST(sem_timedwait, afterDestroyed_isUndefinedBehavior) { + if (!IsModeDbg()) return; + int val; + sem_t sem; + SPAWN(fork); + ASSERT_SYS(0, 0, sem_init(&sem, 0, 0)); + ASSERT_SYS(0, 0, sem_destroy(&sem)); + IgnoreStderr(); + sem_timedwait(&sem, 0); + EXITS(77); +} + void *Worker(void *arg) { int rc; sem_t **s = arg; @@ -60,9 +168,14 @@ TEST(sem_timedwait, threads) { } TEST(sem_timedwait, processes) { - if (IsOpenbsd()) return; // TODO(jart): fix me int i, r, rc, n = 4, pshared = 1; sem_t *sm = _mapshared(FRAMESIZE), *s[2] = {sm, sm + 1}; + if (IsOpenbsd()) { + // TODO(jart): why? + ASSERT_SYS(EPERM, -1, sem_init(s[0], pshared, 0)); + ASSERT_SYS(0, 0, munmap(sm, FRAMESIZE)); + return; + } ASSERT_SYS(0, 0, sem_init(s[0], pshared, 0)); ASSERT_SYS(0, 0, sem_init(s[1], pshared, 0)); for (i = 0; i < n; ++i) { diff --git a/third_party/dlmalloc/dlmalloc.c b/third_party/dlmalloc/dlmalloc.c index 1df8c7128..250eee7b0 100644 --- a/third_party/dlmalloc/dlmalloc.c +++ b/third_party/dlmalloc/dlmalloc.c @@ -15,6 +15,7 @@ #include "libc/str/str.h" #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/prot.h" +#include "libc/thread/thread.h" #include "third_party/dlmalloc/dlmalloc.h" #include "third_party/dlmalloc/vespene.internal.h" // clang-format off @@ -29,6 +30,8 @@ #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 IsTiny() #define INSECURE 1 diff --git a/third_party/dlmalloc/init.inc b/third_party/dlmalloc/init.inc index 51f774542..0c5b65829 100644 --- a/third_party/dlmalloc/init.inc +++ b/third_party/dlmalloc/init.inc @@ -3,9 +3,9 @@ /* ---------------------------- setting mparams -------------------------- */ #if LOCK_AT_FORK -static void pre_fork(void) { ACQUIRE_LOCK(&(gm)->mutex); } -static void post_fork_parent(void) { RELEASE_LOCK(&(gm)->mutex); } -static void post_fork_child(void) { INITIAL_LOCK(&(gm)->mutex); } +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) { INITIAL_LOCK(&(gm)->mutex); } #endif /* LOCK_AT_FORK */ /* Initialize mparams */ @@ -67,8 +67,11 @@ static int init_mparams(void) { gm->mflags = mparams.default_mflags; (void)INITIAL_LOCK(&gm->mutex); #endif + #if LOCK_AT_FORK - pthread_atfork(&pre_fork, &post_fork_parent, &post_fork_child); + pthread_atfork(&dlmalloc_pre_fork, + &dlmalloc_post_fork_parent, + &dlmalloc_post_fork_child); #endif { @@ -92,6 +95,7 @@ static int init_mparams(void) { } RELEASE_MALLOC_GLOBAL_LOCK(); + return 1; } diff --git a/third_party/dlmalloc/locks.inc b/third_party/dlmalloc/locks.inc index 53f11739e..5c601757b 100644 --- a/third_party/dlmalloc/locks.inc +++ b/third_party/dlmalloc/locks.inc @@ -64,7 +64,7 @@ static inline int malloc_unlock(atomic_int *lk) { #define ACQUIRE_LOCK(lk) malloc_lock(lk) #define RELEASE_LOCK(lk) malloc_unlock(lk) #define TRY_LOCK(lk) malloc_trylock(lk) -#define INITIAL_LOCK(lk) (*lk = 0, 0) +#define INITIAL_LOCK(lk) (atomic_store_explicit(lk, 0, memory_order_relaxed), 0) #define DESTROY_LOCK(lk) #define ACQUIRE_MALLOC_GLOBAL_LOCK() ACQUIRE_LOCK(&malloc_global_mutex); #define RELEASE_MALLOC_GLOBAL_LOCK() RELEASE_LOCK(&malloc_global_mutex); diff --git a/third_party/dlmalloc/statistics.inc b/third_party/dlmalloc/statistics.inc index 16a9c4aba..6df79d0b6 100644 --- a/third_party/dlmalloc/statistics.inc +++ b/third_party/dlmalloc/statistics.inc @@ -1,5 +1,4 @@ // clang-format off -#include "libc/intrin/kprintf.h" /* ----------------------------- statistics ------------------------------ */ diff --git a/third_party/nsync/README.cosmo b/third_party/nsync/README.cosmo new file mode 100644 index 000000000..2ebdf744b --- /dev/null +++ b/third_party/nsync/README.cosmo @@ -0,0 +1,19 @@ +DESCRIPTION + + *NSYNC is a synchronization primitives library. + +LICENSE + + Apache 2.0 + +ORIGIN + + git@github.com:google/nsync + commit ac5489682760393fe21bd2a8e038b528442412a7 + Author: Mike Burrows + Date: Wed Jun 1 16:47:52 2022 -0700 + +LOCAL CHANGES + + - nsync_malloc_() is implemented as kmalloc() + - nsync_mu_semaphore uses Cosmopolitan Futexes diff --git a/third_party/nsync/common.c b/third_party/nsync/common.c index ef6e3ac26..4845e8512 100644 --- a/third_party/nsync/common.c +++ b/third_party/nsync/common.c @@ -15,6 +15,7 @@ │ See the License for the specific language governing permissions and │ │ limitations under the License. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/kmalloc.h" #include "libc/mem/mem.h" #include "libc/runtime/runtime.h" #include "libc/thread/thread.h" @@ -23,7 +24,6 @@ #include "third_party/nsync/atomic.internal.h" #include "third_party/nsync/common.internal.h" #include "third_party/nsync/dll.h" -#include "third_party/nsync/malloc.internal.h" #include "third_party/nsync/mu_semaphore.h" #include "third_party/nsync/races.internal.h" #include "third_party/nsync/wait_s.internal.h" @@ -188,7 +188,7 @@ waiter *nsync_waiter_new_ (void) { } ATM_STORE_REL (&free_waiters_mu, 0); /* release store */ if (w == NULL) { /* If free list was empty, allocate an item. */ - w = (waiter *) nsync_malloc_ (sizeof (*w)); + w = (waiter *) kmalloc (sizeof (*w)); w->tag = WAITER_TAG; w->nw.tag = NSYNC_WAITER_TAG; nsync_mu_semaphore_init (&w->sem); diff --git a/third_party/nsync/common.internal.h b/third_party/nsync/common.internal.h index d67120ca3..fba32eb4f 100644 --- a/third_party/nsync/common.internal.h +++ b/third_party/nsync/common.internal.h @@ -13,7 +13,9 @@ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ -#ifndef NSYNC_DEBUG +#ifdef MODE_DBG +#define NSYNC_DEBUG 1 +#else #define NSYNC_DEBUG 0 #endif diff --git a/third_party/nsync/futex.c b/third_party/nsync/futex.c index 75dd4c5d1..377402b97 100644 --- a/third_party/nsync/futex.c +++ b/third_party/nsync/futex.c @@ -43,8 +43,6 @@ #include "third_party/nsync/time.h" // clang-format off -/* futex() polyfill w/ sched_yield() fallback */ - #define FUTEX_WAIT_BITS_ FUTEX_BITSET_MATCH_ANY int _futex (atomic_int *, int, int, const struct timespec *, int *, int); @@ -161,6 +159,11 @@ int nsync_futex_wait_ (atomic_int *w, int expect, char pshare, struct timespec * op |= FUTEX_PRIVATE_FLAG_; } + LOCKTRACE ("futex(%t [%d], %s, %#x, %s) → ...", + w, atomic_load_explicit (w, memory_order_relaxed), + DescribeFutexOp (op), expect, + DescribeTimespec (0, timeout)); + if (FUTEX_IS_SUPPORTED) { if (IsWindows ()) { // Windows 8 futexes don't support multiple processes :( @@ -199,7 +202,8 @@ int nsync_futex_wait_ (atomic_int *w, int expect, char pshare, struct timespec * } STRACE ("futex(%t [%d], %s, %#x, %s) → %s", - w, *w, DescribeFutexOp (op), expect, + w, atomic_load_explicit (w, memory_order_relaxed), + DescribeFutexOp (op), expect, DescribeTimespec (0, timeout), DescribeErrnoResult (rc)); @@ -208,7 +212,7 @@ int nsync_futex_wait_ (atomic_int *w, int expect, char pshare, struct timespec * int nsync_futex_wake_ (atomic_int *w, int count, char pshare) { int e, rc, op, fop; - int wake (void *, int, int) asm ("_futex"); + int wake (atomic_int *, int, int) asm ("_futex"); ASSERT (count == 1 || count == INT_MAX); @@ -245,8 +249,8 @@ int nsync_futex_wake_ (atomic_int *w, int count, char pshare) { } STRACE ("futex(%t [%d], %s, %d) → %s", - w, *w, DescribeFutexOp(op), - count, DescribeErrnoResult(rc)); + w, atomic_load_explicit (w, memory_order_relaxed), + DescribeFutexOp(op), count, DescribeErrnoResult(rc)); return rc; } diff --git a/third_party/nsync/malloc.internal.h b/third_party/nsync/malloc.internal.h deleted file mode 100644 index e4453e410..000000000 --- a/third_party/nsync/malloc.internal.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef NSYNC_MALLOC_INTERNAL_H_ -#define NSYNC_MALLOC_INTERNAL_H_ -#if !(__ASSEMBLER__ + __LINKER__ + 0) -COSMOPOLITAN_C_START_ - -void *nsync_malloc_(size_t); - -COSMOPOLITAN_C_END_ -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ -#endif /* NSYNC_MALLOC_INTERNAL_H_ */ diff --git a/tool/emacs/cosmo-c-constants.el b/tool/emacs/cosmo-c-constants.el index 9164a86f7..824aab5ca 100644 --- a/tool/emacs/cosmo-c-constants.el +++ b/tool/emacs/cosmo-c-constants.el @@ -27,6 +27,7 @@ "__STDC_NO_COMPLEX__" "__STDC_NO_THREADS__" "__STDC_NO_VLA__" + "__FLT_EVAL_METHOD__" "__STDC_WANT_LIB_EXT1__")) (defconst cosmo-c-constants-limits