From 80b211e314512fc8dd77159ac162bcc13cd89c08 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Sat, 14 May 2022 04:33:58 -0700 Subject: [PATCH] Add raw memory visualization tool to redbean This change introduces a `-W /dev/pts/1` flag to redbean. What it does is use the mincore() system call to create a dual-screen terminal display that lets you troubleshoot the virtual address space. This is useful since page faults are an important thing to consider when using a forking web server. Now we have a colorful visualization of which pages are going to fault and which ones are resident in memory. The memory monitor, if enabled, spawns as a thread that just outputs ANSI codes to the second terminal in a loop. In order to make this happen using the new clone() polyfill, stdio is now thread safe. This change also introduces some new demo pages to redbean. It also polishes the demos we already have, to look a bit nicer and more presentable for the upcoming release, with better explanations too. --- libc/calls/calls.h | 19 +- libc/calls/fchown.c | 8 +- libc/calls/internal.h | 1 + libc/calls/mincore.c | 31 +++ libc/calls/nanosleep.c | 6 +- libc/intrin/kprintf.greg.c | 30 --- libc/intrin/spinlock.h | 69 +++++-- libc/log/backtrace2.greg.c | 11 +- libc/log/log.h | 78 ++++---- libc/log/oncrash.c | 14 +- libc/log/vflogf.c | 17 +- libc/runtime/clone.c | 12 +- libc/runtime/cosmo.S | 10 + libc/runtime/printargs.greg.c | 14 +- libc/stdio/clearerr.c | 2 +- libc/stdio/fclose.c | 1 + libc/stdio/feof.c | 2 +- libc/stdio/ferror.c | 2 +- libc/stdio/fflush.c | 29 ++- libc/stdio/fflush.internal.h | 3 +- libc/stdio/fgetc.c | 4 +- libc/stdio/fgets.c | 22 ++- libc/stdio/fgetwc.c | 8 +- libc/stdio/fgetws.c | 6 +- libc/stdio/fileno.c | 2 +- libc/stdio/flockfile.c | 17 +- libc/stdio/flushlbf.c | 13 +- libc/stdio/fputc.c | 4 +- libc/stdio/fputs.c | 4 +- libc/stdio/fputwc.c | 4 +- libc/stdio/fputws.c | 12 +- libc/stdio/fread.c | 2 +- libc/stdio/freopen.c | 13 +- libc/stdio/fseeko.c | 13 +- libc/stdio/fsetlocking.c | 4 +- libc/stdio/ftello.c | 23 ++- libc/stdio/ftrylockfile.c | 28 +++ libc/stdio/funlockfile.c | 27 +++ libc/stdio/fwrite.c | 2 +- libc/stdio/getc.c | 4 +- libc/stdio/getchar.c | 6 +- libc/stdio/getdelim.c | 35 ++-- libc/stdio/getwc.c | 4 +- libc/stdio/getwchar.c | 4 +- libc/stdio/putc.c | 4 +- libc/stdio/putchar.c | 4 +- libc/stdio/putwc.c | 4 +- libc/stdio/putwchar.c | 4 +- libc/stdio/stdio.h | 46 ++++- libc/stdio/ungetc.c | 4 +- libc/stdio/ungetwc.c | 2 +- libc/stdio/unlocked.h | 36 ---- libc/stdio/unlocked/clearerr_unlocked.S | 17 +- libc/stdio/unlocked/feof_unlocked.S | 17 +- libc/stdio/unlocked/ferror_unlocked.S | 17 +- libc/stdio/unlocked/fflush_unlocked.S | 17 +- libc/stdio/unlocked/fgetc_unlocked.S | 17 +- libc/stdio/unlocked/fgets_unlocked.S | 24 ++- libc/stdio/unlocked/fgetwc_unlocked.S | 17 +- libc/stdio/unlocked/fgetws_unlocked.S | 22 ++- libc/stdio/unlocked/fileno_unlocked.S | 16 +- libc/stdio/unlocked/fputc_unlocked.S | 18 +- libc/stdio/unlocked/fputs_unlocked.S | 22 ++- libc/stdio/unlocked/fputwc_unlocked.S | 18 +- libc/stdio/unlocked/fputws_unlocked.S | 22 ++- libc/stdio/unlocked/fread_unlocked.S | 20 +- libc/stdio/unlocked/fwrite_unlocked.S | 20 +- libc/stdio/unlocked/getc_unlocked.S | 17 +- libc/stdio/unlocked/getchar_unlocked.S | 18 +- libc/stdio/unlocked/getwc_unlocked.S | 17 +- libc/stdio/unlocked/getwchar_unlocked.S | 18 +- libc/stdio/unlocked/putc_unlocked.S | 18 +- libc/stdio/unlocked/putchar_unlocked.S | 19 +- libc/stdio/unlocked/putwc_unlocked.S | 18 +- libc/stdio/unlocked/putwchar_unlocked.S | 20 +- libc/stdio/unlocked/stdio_unlock.S | 70 +++++++ libc/stdio/unlocked/ungetc_unlocked.S | 30 +++ libc/stdio/unlocked/ungetwc_unlocked.S | 31 +++ libc/stdio/vfprintf.c | 16 +- libc/sysv/calls/mincore.s | 2 - libc/sysv/calls/sys_mincore.s | 2 + libc/sysv/syscalls.sh | 2 +- libc/time/difftime.c | 1 + libc/time/localtime.c | 47 +---- libc/time/tz.internal.h | 3 +- libc/zipos/find.c | 2 + test/libc/rand/rand64_test.c | 7 +- test/libc/runtime/clone_test.c | 3 +- third_party/lua/liolib.c | 2 +- third_party/lua/lmem.c | 2 + third_party/lz4cli/lz4cli.c | 1 + third_party/python/Objects/fileobject.c | 1 - third_party/python/pyconfig.h | 6 +- third_party/sqlite3/os_unix.c | 2 + third_party/sqlite3/sqlite3.mk | 1 + tool/net/demo/.lua/mymodule.lua | 3 - tool/net/demo/binarytrees.lua | 189 ++++++++++++++++++ tool/net/demo/call-lua-module.lua | 49 +++++ tool/net/demo/fetch.lua | 44 ++--- tool/net/demo/hello.lua | 4 - tool/net/demo/maxmind.lua | 59 +++++- tool/net/demo/store-asset.lua | 80 ++++++++ tool/net/help.txt | 1 + tool/net/lunix.c | 1 + tool/net/net.mk | 8 +- tool/net/redbean.c | 253 +++++++++++++++++++++--- 106 files changed, 1483 insertions(+), 592 deletions(-) create mode 100644 libc/calls/mincore.c create mode 100644 libc/stdio/ftrylockfile.c create mode 100644 libc/stdio/funlockfile.c delete mode 100644 libc/stdio/unlocked.h create mode 100644 libc/stdio/unlocked/stdio_unlock.S create mode 100644 libc/stdio/unlocked/ungetc_unlocked.S create mode 100644 libc/stdio/unlocked/ungetwc_unlocked.S delete mode 100644 libc/sysv/calls/mincore.s create mode 100644 libc/sysv/calls/sys_mincore.s create mode 100644 tool/net/demo/binarytrees.lua create mode 100644 tool/net/demo/call-lua-module.lua delete mode 100644 tool/net/demo/hello.lua create mode 100644 tool/net/demo/store-asset.lua diff --git a/libc/calls/calls.h b/libc/calls/calls.h index afb1060c5..5d29a9a98 100644 --- a/libc/calls/calls.h +++ b/libc/calls/calls.h @@ -21,14 +21,16 @@ #define _POSIX2_VERSION _POSIX_VERSION #define _XOPEN_VERSION 700 -#define EOF -1 /* end of file */ -#define WEOF -1u /* end of file (multibyte) */ -#define _IOFBF 0 /* fully buffered */ -#define _IOLBF 1 /* line buffered */ -#define _IONBF 2 /* no buffering */ -#define SEEK_SET 0 /* relative to beginning */ -#define SEEK_CUR 1 /* relative to current position */ -#define SEEK_END 2 /* relative to end */ +#define EOF -1 /* end of file */ +#define WEOF -1u /* end of file (multibyte) */ +#define _IOFBF 0 /* fully buffered */ +#define _IOLBF 1 /* line buffered */ +#define _IONBF 2 /* no buffering */ +#define SEEK_SET 0 /* relative to beginning */ +#define SEEK_CUR 1 /* relative to current position */ +#define SEEK_END 2 /* relative to end */ +#define __WALL 0x40000000 /* Wait on all children, regardless of type */ +#define __WCLONE 0x80000000 /* Wait only on non-SIGCHLD children */ #define SIG_ERR ((void (*)(int))(-1)) #define SIG_DFL ((void *)0) @@ -147,6 +149,7 @@ int linkat(int, const char *, int, const char *, int); int lstat(const char *, struct stat *); int lutimes(const char *, const struct timeval[2]); int madvise(void *, uint64_t, int); +int mincore(void *, size_t, unsigned char *); int mkdir(const char *, uint32_t); int mkdirat(int, const char *, uint32_t); int mkfifo(const char *, uint32_t); diff --git a/libc/calls/fchown.c b/libc/calls/fchown.c index 0baa409df..6f7f5f1c5 100644 --- a/libc/calls/fchown.c +++ b/libc/calls/fchown.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/calls/internal.h" +#include "libc/calls/strace.internal.h" #include "libc/sysv/consts/at.h" /** @@ -28,8 +29,11 @@ * @return 0 on success, or -1 w/ errno * @see /etc/passwd for user ids * @see /etc/group for group ids + * @raises ENOSYS on Windows */ int fchown(int fd, uint32_t uid, uint32_t gid) { - /* TODO(jart): Windows? */ - return sys_fchown(fd, uid, gid); + int rc; + rc = sys_fchown(fd, uid, gid); + STRACE("fchown(%d, %d, %d) → %d% m", fd, uid, gid, rc); + return rc; } diff --git a/libc/calls/internal.h b/libc/calls/internal.h index c50bba12d..8ec7a418c 100644 --- a/libc/calls/internal.h +++ b/libc/calls/internal.h @@ -176,6 +176,7 @@ i32 sys_lseek(i32, i64, i64, i64) hidden; i32 sys_lutimes(const char *, const struct timeval *) hidden; i32 sys_madvise(void *, size_t, i32) hidden; i32 sys_memfd_create(const char *, u32) hidden; +i32 sys_mincore(void *, u64, unsigned char *) hidden; i32 sys_mkdirat(i32, const char *, u32) hidden; i32 sys_mkfifo(const char *, u32) hidden; i32 sys_mknod(const char *, u32, u64) hidden; diff --git a/libc/calls/mincore.c b/libc/calls/mincore.c new file mode 100644 index 000000000..16b9cf1f4 --- /dev/null +++ b/libc/calls/mincore.c @@ -0,0 +1,31 @@ +/*-*- 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/internal.h" +#include "libc/calls/strace.internal.h" + +/** + * Tells you which pages are resident in memory. + */ +int mincore(void *addr, size_t length, unsigned char *vec) { + int rc; + rc = sys_mincore(addr, length, vec); + POLLTRACE("mincore(%p, %'zu, %p) → %d% m", addr, length, vec, rc); + return rc; +} diff --git a/libc/calls/nanosleep.c b/libc/calls/nanosleep.c index cffd10ddd..226eeba81 100644 --- a/libc/calls/nanosleep.c +++ b/libc/calls/nanosleep.c @@ -45,9 +45,9 @@ noinstrument int nanosleep(const struct timespec *req, struct timespec *rem) { rc = sys_nanosleep_nt(req, rem); } if (!__time_critical) { - STRACE("nanosleep(%s, [%s]) → %d% m", - DescribeTimespec(buf[0], sizeof(buf[0]), rc, req), - DescribeTimespec(buf[1], sizeof(buf[1]), rc, rem), rc); + POLLTRACE("nanosleep(%s, [%s]) → %d% m", + DescribeTimespec(buf[0], sizeof(buf[0]), rc, req), + DescribeTimespec(buf[1], sizeof(buf[1]), rc, rem), rc); } return rc; } diff --git a/libc/intrin/kprintf.greg.c b/libc/intrin/kprintf.greg.c index fb1b5b60a..90f8177c5 100644 --- a/libc/intrin/kprintf.greg.c +++ b/libc/intrin/kprintf.greg.c @@ -105,7 +105,6 @@ privileged static char *kemitquote(char *p, char *e, signed char t, privileged static unsigned long long kgetint(va_list va, signed char t, bool s) { -#ifdef __LP64__ int bits; unsigned long long x; x = va_arg(va, unsigned long); @@ -119,35 +118,6 @@ privileged static unsigned long long kgetint(va_list va, signed char t, } } return x; -#else - if (s) { - switch (t) { - case -2: - return (signed char)va_arg(va, int); - case -1: - return (signed short)va_arg(va, int); - default: - return va_arg(va, signed int); - case 1: - return va_arg(va, signed long); - case 2: - return va_arg(va, signed long long); - } - } else { - switch (t) { - case -2: - return (unsigned char)va_arg(va, int); - case -1: - return (unsigned short)va_arg(va, int); - default: - return va_arg(va, unsigned int); - case 1: - return va_arg(va, unsigned long); - case 2: - return va_arg(va, unsigned long long); - } - } -#endif } privileged static inline bool kiskernelpointer(const void *p) { diff --git a/libc/intrin/spinlock.h b/libc/intrin/spinlock.h index 28e6ddb01..e66e1aec2 100644 --- a/libc/intrin/spinlock.h +++ b/libc/intrin/spinlock.h @@ -1,34 +1,65 @@ #ifndef COSMOPOLITAN_LIBC_INTRIN_SPINLOCK_H_ #define COSMOPOLITAN_LIBC_INTRIN_SPINLOCK_H_ +#include "libc/bits/weaken.h" +#include "libc/calls/calls.h" +#include "libc/intrin/kprintf.h" +#include "libc/log/backtrace.internal.h" +#include "libc/log/log.h" -#ifdef TINY -#define _spinlock(lock) \ - do { \ - while (__atomic_test_and_set(lock, __ATOMIC_SEQ_CST)) { \ - __builtin_ia32_pause(); \ - } \ - } while (0) +#if defined(_SPINLOCK_DEBUG) +#define _spinlock(lock) _spinlock_debug(lock) +#elif defined(TINY) +#define _spinlock(lock) _spinlock_tiny(lock) #else -#define _spinlock(lock) \ - do { \ - for (;;) { \ - typeof(*(lock)) x; \ - __atomic_load(lock, &x, __ATOMIC_RELAXED); \ - if (!x && !__atomic_test_and_set(lock, __ATOMIC_SEQ_CST)) { \ - break; \ - } else { \ - __builtin_ia32_pause(); \ - } \ - } \ - } while (0) +#define _spinlock(lock) _spinlock_optimistic(lock) #endif #define _spunlock(lock) __atomic_clear(lock, __ATOMIC_RELAXED) +#define _trylock(lock) __atomic_test_and_set(lock, __ATOMIC_SEQ_CST) + #define _seizelock(lock) \ do { \ typeof(*(lock)) x = 1; \ __atomic_store(lock, &x, __ATOMIC_SEQ_CST); \ } while (0) +#define _spinlock_tiny(lock) \ + do { \ + while (_trylock(lock)) { \ + __builtin_ia32_pause(); \ + } \ + } while (0) + +#define _spinlock_optimistic(lock) \ + do { \ + for (;;) { \ + typeof(*(lock)) x; \ + __atomic_load(lock, &x, __ATOMIC_RELAXED); \ + if (!x && !_trylock(lock)) { \ + break; \ + } else { \ + __builtin_ia32_pause(); \ + } \ + } \ + } while (0) + +#define _spinlock_debug(lock) \ + do { \ + typeof(*(lock)) me, owner; \ + me = gettid(); \ + if (_trylock(lock)) { \ + __atomic_load(lock, &owner, __ATOMIC_RELAXED); \ + if (owner == me) { \ + kprintf("%s:%d: warning: possible spinlock re-entry in %s()\n", \ + __FILE__, __LINE__, __FUNCTION__); \ + if (weaken(ShowBacktrace)) { \ + weaken(ShowBacktrace)(2, 0); \ + } \ + } \ + _spinlock_optimistic(lock); \ + } \ + *lock = me; \ + } while (0) + #endif /* COSMOPOLITAN_LIBC_INTRIN_SPINLOCK_H_ */ diff --git a/libc/log/backtrace2.greg.c b/libc/log/backtrace2.greg.c index 376a2a580..611d05688 100644 --- a/libc/log/backtrace2.greg.c +++ b/libc/log/backtrace2.greg.c @@ -174,15 +174,12 @@ static int PrintBacktrace(int fd, const struct StackFrame *bp) { void ShowBacktrace(int fd, const struct StackFrame *bp) { #ifdef __FNO_OMIT_FRAME_POINTER__ /* asan runtime depends on this function */ - int st, ft; - st = __strace, __strace = 0; - ft = g_ftrace, g_ftrace = 0; + __atomic_fetch_sub(&g_ftrace, 1, __ATOMIC_RELAXED); + __atomic_fetch_sub(&__strace, 1, __ATOMIC_RELAXED); if (!bp) bp = __builtin_frame_address(0); - PrintBacktrace(fd, bp); - - __strace = st; - g_ftrace = ft; + __atomic_fetch_add(&__strace, 1, __ATOMIC_RELAXED); + __atomic_fetch_add(&g_ftrace, 1, __ATOMIC_RELAXED); #else (fprintf)(stderr, "ShowBacktrace() needs these flags to show C backtrace:\n" "\t-D__FNO_OMIT_FRAME_POINTER__\n" diff --git a/libc/log/log.h b/libc/log/log.h index 3c804d0a1..1c9c8b93b 100644 --- a/libc/log/log.h +++ b/libc/log/log.h @@ -1,6 +1,7 @@ #ifndef COSMOPOLITAN_LIBC_LOG_LOG_H_ #define COSMOPOLITAN_LIBC_LOG_LOG_H_ #include "libc/bits/likely.h" +#include "libc/bits/weaken.h" #include "libc/calls/struct/rusage.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/struct/winsize.h" @@ -77,23 +78,24 @@ extern unsigned __log_level; /* log level for runtime check */ // log a message with the specified log level (not checking if LOGGABLE) #define LOGF(LEVEL, FMT, ...) \ do { \ - --g_ftrace; \ + __atomic_fetch_sub(&g_ftrace, 1, __ATOMIC_RELAXED); \ flogf(LEVEL, __FILE__, __LINE__, NULL, FMT, ##__VA_ARGS__); \ - ++g_ftrace; \ + __atomic_fetch_add(&g_ftrace, 1, __ATOMIC_RELAXED); \ } while (0) // die with an error message without backtrace and debugger invocation #define DIEF(FMT, ...) \ do { \ - --g_ftrace; \ + __atomic_fetch_sub(&g_ftrace, 1, __ATOMIC_RELAXED); \ flogf(kLogError, __FILE__, __LINE__, NULL, FMT, ##__VA_ARGS__); \ + if (weaken(__die)) weaken(__die)(); \ exit(1); \ unreachable; \ } while (0) #define FATALF(FMT, ...) \ do { \ - --g_ftrace; \ + __atomic_fetch_sub(&g_ftrace, 1, __ATOMIC_RELAXED); \ ffatalf(kLogFatal, __FILE__, __LINE__, NULL, FMT, ##__VA_ARGS__); \ unreachable; \ } while (0) @@ -101,78 +103,78 @@ extern unsigned __log_level; /* log level for runtime check */ #define ERRORF(FMT, ...) \ do { \ if (LOGGABLE(kLogError)) { \ - --g_ftrace; \ + __atomic_fetch_sub(&g_ftrace, 1, __ATOMIC_RELAXED); \ flogf(kLogError, __FILE__, __LINE__, NULL, FMT, ##__VA_ARGS__); \ - ++g_ftrace; \ + __atomic_fetch_add(&g_ftrace, 1, __ATOMIC_RELAXED); \ } \ } while (0) #define WARNF(FMT, ...) \ do { \ if (LOGGABLE(kLogWarn)) { \ - --g_ftrace; \ + __atomic_fetch_sub(&g_ftrace, 1, __ATOMIC_RELAXED); \ flogf(kLogWarn, __FILE__, __LINE__, NULL, FMT, ##__VA_ARGS__); \ - ++g_ftrace; \ + __atomic_fetch_add(&g_ftrace, 1, __ATOMIC_RELAXED); \ } \ } while (0) #define INFOF(FMT, ...) \ do { \ if (LOGGABLE(kLogInfo)) { \ - --g_ftrace; \ + __atomic_fetch_sub(&g_ftrace, 1, __ATOMIC_RELAXED); \ flogf(kLogInfo, __FILE__, __LINE__, NULL, FMT, ##__VA_ARGS__); \ - ++g_ftrace; \ + __atomic_fetch_add(&g_ftrace, 1, __ATOMIC_RELAXED); \ } \ } while (0) #define VERBOSEF(FMT, ...) \ do { \ if (LOGGABLE(kLogVerbose)) { \ - --g_ftrace; \ + __atomic_fetch_sub(&g_ftrace, 1, __ATOMIC_RELAXED); \ fverbosef(kLogVerbose, __FILE__, __LINE__, NULL, FMT, ##__VA_ARGS__); \ - ++g_ftrace; \ + __atomic_fetch_add(&g_ftrace, 1, __ATOMIC_RELAXED); \ } \ } while (0) #define DEBUGF(FMT, ...) \ do { \ if (UNLIKELY(LOGGABLE(kLogDebug))) { \ - --g_ftrace; \ + __atomic_fetch_sub(&g_ftrace, 1, __ATOMIC_RELAXED); \ fdebugf(kLogDebug, __FILE__, __LINE__, NULL, FMT, ##__VA_ARGS__); \ - ++g_ftrace; \ + __atomic_fetch_add(&g_ftrace, 1, __ATOMIC_RELAXED); \ } \ } while (0) #define NOISEF(FMT, ...) \ do { \ if (UNLIKELY(LOGGABLE(kLogNoise))) { \ - --g_ftrace; \ + __atomic_fetch_sub(&g_ftrace, 1, __ATOMIC_RELAXED); \ fnoisef(kLogNoise, __FILE__, __LINE__, NULL, FMT, ##__VA_ARGS__); \ - ++g_ftrace; \ + __atomic_fetch_add(&g_ftrace, 1, __ATOMIC_RELAXED); \ } \ } while (0) #define FLOGF(F, FMT, ...) \ do { \ if (LOGGABLE(kLogInfo)) { \ - --g_ftrace; \ + __atomic_fetch_sub(&g_ftrace, 1, __ATOMIC_RELAXED); \ flogf(kLogInfo, __FILE__, __LINE__, F, FMT, ##__VA_ARGS__); \ - ++g_ftrace; \ + __atomic_fetch_add(&g_ftrace, 1, __ATOMIC_RELAXED); \ } \ } while (0) #define FWARNF(F, FMT, ...) \ do { \ if (LOGGABLE(kLogWarn)) { \ - --g_ftrace; \ + __atomic_fetch_sub(&g_ftrace, 1, __ATOMIC_RELAXED); \ flogf(kLogWarn, __FILE__, __LINE__, F, FMT, ##__VA_ARGS__); \ - ++g_ftrace; \ + __atomic_fetch_add(&g_ftrace, 1, __ATOMIC_RELAXED); \ } \ } while (0) #define FFATALF(F, FMT, ...) \ do { \ - --g_ftrace; \ + __atomic_fetch_sub(&g_ftrace, 1, __ATOMIC_RELAXED); \ ffatalf(kLogFatal, __FILE__, __LINE__, F, FMT, ##__VA_ARGS__); \ unreachable; \ } while (0) @@ -180,18 +182,18 @@ extern unsigned __log_level; /* log level for runtime check */ #define FDEBUGF(F, FMT, ...) \ do { \ if (UNLIKELY(LOGGABLE(kLogDebug))) { \ - --g_ftrace; \ + __atomic_fetch_sub(&g_ftrace, 1, __ATOMIC_RELAXED); \ fdebugf(kLogDebug, __FILE__, __LINE__, F, FMT, ##__VA_ARGS__); \ - ++g_ftrace; \ + __atomic_fetch_add(&g_ftrace, 1, __ATOMIC_RELAXED); \ } \ } while (0) #define FNOISEF(F, FMT, ...) \ do { \ if (UNLIKELY(LOGGABLE(kLogNoise))) { \ - --g_ftrace; \ + __atomic_fetch_sub(&g_ftrace, 1, __ATOMIC_RELAXED); \ fnoisef(kLogNoise, __FILE__, __LINE__, F, FMT, ##__VA_ARGS__); \ - ++g_ftrace; \ + __atomic_fetch_add(&g_ftrace, 1, __ATOMIC_RELAXED); \ } \ } while (0) @@ -204,25 +206,25 @@ extern unsigned __log_level; /* log level for runtime check */ int e = errno; \ autotype(FORM) Ax = (FORM); \ if (UNLIKELY(Ax == (typeof(Ax))(-1)) && LOGGABLE(kLogWarn)) { \ - --g_ftrace; \ + __atomic_fetch_sub(&g_ftrace, 1, __ATOMIC_RELAXED); \ __logerrno(__FILE__, __LINE__, #FORM); \ - ++g_ftrace; \ + __atomic_fetch_add(&g_ftrace, 1, __ATOMIC_RELAXED); \ errno = e; \ } \ Ax; \ }) -#define LOGIFNULL(FORM) \ - ({ \ - int e = errno; \ - autotype(FORM) Ax = (FORM); \ - if (Ax == NULL && LOGGABLE(kLogWarn)) { \ - --g_ftrace; \ - __logerrno(__FILE__, __LINE__, #FORM); \ - ++g_ftrace; \ - errno = e; \ - } \ - Ax; \ +#define LOGIFNULL(FORM) \ + ({ \ + int e = errno; \ + autotype(FORM) Ax = (FORM); \ + if (Ax == NULL && LOGGABLE(kLogWarn)) { \ + __atomic_fetch_sub(&g_ftrace, 1, __ATOMIC_RELAXED); \ + __logerrno(__FILE__, __LINE__, #FORM); \ + __atomic_fetch_add(&g_ftrace, 1, __ATOMIC_RELAXED); \ + errno = e; \ + } \ + Ax; \ }) /*───────────────────────────────────────────────────────────────────────────│─╗ diff --git a/libc/log/oncrash.c b/libc/log/oncrash.c index 01733176b..b517e2ead 100644 --- a/libc/log/oncrash.c +++ b/libc/log/oncrash.c @@ -275,10 +275,10 @@ static wontreturn relegated noinstrument void __minicrash(int sig, relegated noinstrument void __oncrash(int sig, struct siginfo *si, ucontext_t *ctx) { intptr_t rip; - int gdbpid, err, st, ft; + int gdbpid, err; static bool noreentry, notpossible; - st = __strace, __strace = 0; - ft = g_ftrace, g_ftrace = 0; + __atomic_fetch_sub(&g_ftrace, 1, __ATOMIC_RELAXED); + __atomic_fetch_sub(&__strace, 1, __ATOMIC_RELAXED); if (_lockcmpxchg(&noreentry, false, true)) { if (!__vforked) { rip = ctx ? ctx->uc_mcontext.rip : 0; @@ -306,9 +306,7 @@ relegated noinstrument void __oncrash(int sig, struct siginfo *si, } } else if (sig == SIGTRAP) { /* chances are IsDebuggerPresent() confused strace w/ gdb */ - g_ftrace = ft; - __strace = st; - return; + goto ItsATrap; } else if (_lockcmpxchg(¬possible, false, true)) { __minicrash(sig, si, ctx, "WHILE CRASHING"); } else { @@ -317,5 +315,7 @@ relegated noinstrument void __oncrash(int sig, struct siginfo *si, } } noreentry = false; - ++g_ftrace; +ItsATrap: + __atomic_fetch_add(&__strace, 1, __ATOMIC_RELAXED); + __atomic_fetch_add(&g_ftrace, 1, __ATOMIC_RELAXED); } diff --git a/libc/log/vflogf.c b/libc/log/vflogf.c index 95f6ec0f3..42766b264 100644 --- a/libc/log/vflogf.c +++ b/libc/log/vflogf.c @@ -26,6 +26,7 @@ #include "libc/errno.h" #include "libc/fmt/conv.h" #include "libc/fmt/fmt.h" +#include "libc/intrin/spinlock.h" #include "libc/log/internal.h" #include "libc/log/log.h" #include "libc/math.h" @@ -40,6 +41,7 @@ #define kNontrivialSize (8 * 1000 * 1000) static struct timespec vflogf_ts; +_Alignas(64) static char vflogf_lock; /** * Takes corrective action if logging is on the fritz. @@ -77,17 +79,18 @@ void vflogf_onfail(FILE *f) { */ void(vflogf)(unsigned level, const char *file, int line, FILE *f, const char *fmt, va_list va) { + int bufmode; struct tm tm; long double t2; - int st, bufmode; const char *prog; bool issamesecond; char buf32[32]; int64_t secs, nsec, dots; if (!f) f = __log_file; if (!f) return; - st = __strace; - __strace = 0; + _spinlock(&vflogf_lock); + __atomic_fetch_sub(&__strace, 1, __ATOMIC_RELAXED); + t2 = nowl(); secs = t2; nsec = (t2 - secs) * 1e9L; @@ -95,11 +98,13 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f, dots = issamesecond ? nsec - vflogf_ts.tv_nsec : nsec; vflogf_ts.tv_sec = secs; vflogf_ts.tv_nsec = nsec; + localtime_r(&secs, &tm); strcpy(iso8601(buf32, &tm), issamesecond ? "+" : "."); prog = basename(firstnonnull(program_invocation_name, "unknown")); bufmode = f->bufmode; if (bufmode == _IOLBF) f->bufmode = _IOFBF; + if ((fprintf)(f, "%r%c%s%06ld:%s:%d:%.*s:%d] ", "FEWIVDNT"[level & 7], buf32, rem1000000int64(div1000int64(dots)), file, line, strchrnul(prog, '.') - prog, prog, getpid()) <= 0) { @@ -111,13 +116,17 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f, f->bufmode = _IOLBF; fflush(f); } + if (level == kLogFatal) { __start_fatal(file, line); strcpy(buf32, "unknown"); gethostname(buf32, sizeof(buf32)); (dprintf)(STDERR_FILENO, "fatality %s pid %d\n", buf32, getpid()); + _spunlock(&vflogf_lock); __die(); unreachable; } - __strace = st; + + __atomic_fetch_add(&__strace, 1, __ATOMIC_RELAXED); + _spunlock(&vflogf_lock); } diff --git a/libc/runtime/clone.c b/libc/runtime/clone.c index 3b81514a7..c6295f4e8 100644 --- a/libc/runtime/clone.c +++ b/libc/runtime/clone.c @@ -438,7 +438,7 @@ static int CloneLinux(int (*func)(void *), char *stk, size_t stksz, int flags, * @param flags usually has one of * - `SIGCHLD` will delegate to fork() * - `CLONE_VFORK|CLONE_VM|SIGCHLD` means vfork() - * - `CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND` for threads + * - `CLONE_THREAD|CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND` * as part high bytes, and the low order byte may optionally contain * a signal e.g. SIGCHLD, to enable parent notification on terminate * although the signal isn't supported on non-Linux and non-NetBSD @@ -504,11 +504,11 @@ int clone(int (*func)(void *), void *stk, size_t stksz, int flags, void *arg, // we now assume we're creating a thread // these platforms can't do signals the way linux does - else if (!IsTiny() && - ((stksz < PAGESIZE || (stksz & (PAGESIZE - 1))) || - (flags & - ~(CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_SETTID)) != - (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND))) { + else if (!IsTiny() && ((stksz < PAGESIZE || (stksz & (PAGESIZE - 1))) || + (flags & ~(CLONE_SETTLS | CLONE_PARENT_SETTID | + CLONE_CHILD_SETTID)) != + (CLONE_THREAD | CLONE_VM | CLONE_FS | CLONE_FILES | + CLONE_SIGHAND))) { rc = einval(); } else if (IsXnu()) { rc = CloneXnu(func, stk, stksz, flags, arg, ptid, tls, tlssz, ctid); diff --git a/libc/runtime/cosmo.S b/libc/runtime/cosmo.S index 478e72a72..c14c2a1c3 100644 --- a/libc/runtime/cosmo.S +++ b/libc/runtime/cosmo.S @@ -77,6 +77,16 @@ cosmo: push %rbp .endfn cosmo,weak #if !IsTiny() +// Creates deterministically addressed stack we can use +// +// This helps debugging be more comprehensible, because +// when diagnosing low-level problems when error report +// isn't working, sometimes numbers are all you have to +// go on, and we can't use them if kernel hardening has +// configured that meaningful data to be randomized. +// +// Having deterministic addresses is also key to ensure +// builds, execution, and other things are reproducible .init.start 304,_init_stack testb IsWindows() jnz 9f diff --git a/libc/runtime/printargs.greg.c b/libc/runtime/printargs.greg.c index 8f18b5f02..3d871d8fb 100644 --- a/libc/runtime/printargs.greg.c +++ b/libc/runtime/printargs.greg.c @@ -137,18 +137,18 @@ textstartup void __printargs(const char *prologue) { char **env; sigset_t ss; unsigned i, n; + int e, x, flags; uintptr_t *auxp; struct utsname uts; struct termios termios; - int e, x, st, ft, flags; struct AuxiliaryValue *auxinfo; union { char path[PATH_MAX]; struct pollfd pfds[128]; } u; - st = __strace, __strace = 0; - ft = g_ftrace, g_ftrace = 0; + __atomic_fetch_sub(&g_ftrace, 1, __ATOMIC_RELAXED); + __atomic_fetch_sub(&__strace, 1, __ATOMIC_RELAXED); e = errno; PRINT(""); @@ -454,13 +454,13 @@ textstartup void __printargs(const char *prologue) { kprintf("\n"); PRINT(" c_ispeed = %u", termios.c_ispeed); PRINT(" c_ospeed = %u", termios.c_ospeed); + PRINT(" c_cc[VMIN] = %d", termios.c_cc[VMIN]); + PRINT(" c_cc[VTIME] = %d", termios.c_cc[VTIME]); PRINT(" c_cc[VINTR] = CTRL-%c", CTRL(termios.c_cc[VINTR])); PRINT(" c_cc[VQUIT] = CTRL-%c", CTRL(termios.c_cc[VQUIT])); PRINT(" c_cc[VERASE] = CTRL-%c", CTRL(termios.c_cc[VERASE])); PRINT(" c_cc[VKILL] = CTRL-%c", CTRL(termios.c_cc[VKILL])); PRINT(" c_cc[VEOF] = CTRL-%c", CTRL(termios.c_cc[VEOF])); - PRINT(" c_cc[VTIME] = CTRL-%c", CTRL(termios.c_cc[VTIME])); - PRINT(" c_cc[VMIN] = CTRL-%c", CTRL(termios.c_cc[VMIN])); PRINT(" c_cc[VSTART] = CTRL-%c", CTRL(termios.c_cc[VSTART])); PRINT(" c_cc[VSTOP] = CTRL-%c", CTRL(termios.c_cc[VSTOP])); PRINT(" c_cc[VSUSP] = CTRL-%c", CTRL(termios.c_cc[VSUSP])); @@ -547,7 +547,7 @@ textstartup void __printargs(const char *prologue) { } PRINT(""); - __strace = st; - g_ftrace = ft; + __atomic_fetch_add(&__strace, 1, __ATOMIC_RELAXED); + __atomic_fetch_add(&g_ftrace, 1, __ATOMIC_RELAXED); errno = e; } diff --git a/libc/stdio/clearerr.c b/libc/stdio/clearerr.c index db30cda81..d06719d15 100644 --- a/libc/stdio/clearerr.c +++ b/libc/stdio/clearerr.c @@ -18,6 +18,6 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/stdio/stdio.h" -void clearerr(FILE *f) { +void clearerr_unlocked(FILE *f) { f->state = 0; } diff --git a/libc/stdio/fclose.c b/libc/stdio/fclose.c index cd75a500e..94b5aebe6 100644 --- a/libc/stdio/fclose.c +++ b/libc/stdio/fclose.c @@ -19,6 +19,7 @@ #include "libc/assert.h" #include "libc/calls/calls.h" #include "libc/errno.h" +#include "libc/intrin/spinlock.h" #include "libc/mem/mem.h" #include "libc/runtime/runtime.h" #include "libc/stdio/internal.h" diff --git a/libc/stdio/feof.c b/libc/stdio/feof.c index 6016fc8e3..550abfc94 100644 --- a/libc/stdio/feof.c +++ b/libc/stdio/feof.c @@ -21,6 +21,6 @@ /** * Returns true if stream is in end-of-file state. */ -int feof(FILE *f) { +int feof_unlocked(FILE *f) { return f->state == -1; } diff --git a/libc/stdio/ferror.c b/libc/stdio/ferror.c index 241cb25f4..d44c3a638 100644 --- a/libc/stdio/ferror.c +++ b/libc/stdio/ferror.c @@ -24,6 +24,6 @@ * @note EOF doesn't count * @see feof() */ -errno_t ferror(FILE *f) { +errno_t ferror_unlocked(FILE *f) { return f->state > 0 ? f->state : 0; } diff --git a/libc/stdio/fflush.c b/libc/stdio/fflush.c index 3ea545e91..28f9f18e6 100644 --- a/libc/stdio/fflush.c +++ b/libc/stdio/fflush.c @@ -21,6 +21,7 @@ #include "libc/bits/pushpop.h" #include "libc/calls/calls.h" #include "libc/errno.h" +#include "libc/intrin/spinlock.h" #include "libc/macros.internal.h" #include "libc/mem/mem.h" #include "libc/runtime/runtime.h" @@ -35,49 +36,63 @@ * @param f is the stream handle * @return is 0 on success or -1 on error */ -int fflush(FILE *f) { +int fflush_unlocked(FILE *f) { + int rc = 0; size_t i; if (!f) { + _spinlock(&__fflush.lock); for (i = __fflush.handles.i; i; --i) { if ((f = __fflush.handles.p[i - 1])) { - if (fflush(f) == -1) return -1; + if (fflush(f) == -1) { + rc = -1; + } } } + _spunlock(&__fflush.lock); } else if (f->fd != -1) { - if (__fflush_impl(f) == -1) return -1; + if (__fflush_impl(f) == -1) { + rc = -1; + } } else if (f->beg && f->beg < f->size) { f->buf[f->beg] = 0; } - return 0; + return rc; } textstartup int __fflush_register(FILE *f) { + int rc; size_t i; struct StdioFlush *sf; + _spinlock(&__fflush.lock); sf = &__fflush; if (!sf->handles.p) { sf->handles.p = sf->handles_initmem; pushmov(&sf->handles.n, ARRAYLEN(sf->handles_initmem)); - __cxa_atexit(fflush, 0, 0); + __cxa_atexit(fflush_unlocked, 0, 0); } for (i = sf->handles.i; i; --i) { if (!sf->handles.p[i - 1]) { sf->handles.p[i - 1] = f; + _spunlock(&__fflush.lock); return 0; } } - return append(&sf->handles, &f); + rc = append(&sf->handles, &f); + _spunlock(&__fflush.lock); + return rc; } void __fflush_unregister(FILE *f) { size_t i; struct StdioFlush *sf; + _spinlock(&__fflush.lock); sf = &__fflush; sf = pushpop(sf); for (i = sf->handles.i; i; --i) { if (sf->handles.p[i - 1] == f) { pushmov(&sf->handles.p[i - 1], 0); - return; + break; } } + _spunlock(&__fflush.lock); } diff --git a/libc/stdio/fflush.internal.h b/libc/stdio/fflush.internal.h index d49156dd1..1dd682498 100644 --- a/libc/stdio/fflush.internal.h +++ b/libc/stdio/fflush.internal.h @@ -10,11 +10,12 @@ struct StdioFlushHandles { }; struct StdioFlush { + char lock; struct StdioFlushHandles handles; FILE *handles_initmem[8]; }; -extern struct StdioFlush __fflush hidden; +hidden extern struct StdioFlush __fflush; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/stdio/fgetc.c b/libc/stdio/fgetc.c index 731e4b3e7..9ed38a2fb 100644 --- a/libc/stdio/fgetc.c +++ b/libc/stdio/fgetc.c @@ -22,12 +22,12 @@ * Reads byte from stream. * @return byte in range 0..255, or -1 w/ errno */ -int fgetc(FILE *f) { +int fgetc_unlocked(FILE *f) { unsigned char b[1]; if (f->beg < f->end) { return f->buf[f->beg++] & 0xff; } else { - if (!fread(b, 1, 1, f)) return -1; + if (!fread_unlocked(b, 1, 1, f)) return -1; return b[0]; } } diff --git a/libc/stdio/fgets.c b/libc/stdio/fgets.c index 7e7d77427..85d7ebc57 100644 --- a/libc/stdio/fgets.c +++ b/libc/stdio/fgets.c @@ -16,28 +16,36 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/assert.h" #include "libc/errno.h" #include "libc/stdio/stdio.h" /** - * Reads content from stream. + * Reads line from stream. * * This function is similar to getline() except it'll truncate lines * exceeding size. The line ending marker is included and may be removed * using _chomp(). + * + * @param s is output buffer + * @param size is capacity of s + * @param f is non-null file oject stream pointer + * @return s on success, NULL on error, or NULL if EOF happens when + * zero characters have been read */ -char *fgets(char *s, int size, FILE *f) { +char *fgets_unlocked(char *s, int size, FILE *f) { int c; char *p; p = s; if (size > 0) { while (--size > 0) { - if ((c = getc(f)) == -1) { - if (ferror(f) == EINTR) continue; - break; + if ((c = fgetc_unlocked(f)) == -1) { + if (ferror_unlocked(f) == EINTR) { + continue; + } else { + break; + } } - *p++ = c & 0xff; + *p++ = c & 255; if (c == '\n') break; } *p = '\0'; diff --git a/libc/stdio/fgetwc.c b/libc/stdio/fgetwc.c index 641123648..7f5a421d5 100644 --- a/libc/stdio/fgetwc.c +++ b/libc/stdio/fgetwc.c @@ -24,12 +24,12 @@ * Reads UTF-8 character from stream. * @return wide character or -1 on EOF or error */ -wint_t fgetwc(FILE *f) { +wint_t fgetwc_unlocked(FILE *f) { int c, n; wint_t b, x, y; if (f->beg < f->end) { b = f->buf[f->beg++] & 0xff; - } else if ((c = fgetc(f)) != -1) { + } else if ((c = fgetc_unlocked(f)) != -1) { b = c; } else { return -1; @@ -38,12 +38,12 @@ wint_t fgetwc(FILE *f) { n = ThomPikeLen(b); x = ThomPikeByte(b); while (--n) { - if ((c = fgetc(f)) == -1) return -1; + if ((c = fgetc_unlocked(f)) == -1) return -1; y = c; if (ThomPikeCont(y)) { x = ThomPikeMerge(x, y); } else { - ungetc(y, f); + ungetc_unlocked(y, f); return b; } } diff --git a/libc/stdio/fgetws.c b/libc/stdio/fgetws.c index 3fdd3b3b1..7cbdc85d2 100644 --- a/libc/stdio/fgetws.c +++ b/libc/stdio/fgetws.c @@ -23,13 +23,13 @@ /** * Reads UTF-8 content from stream into UTF-32 buffer. */ -wchar_t *fgetws(wchar_t *s, int size, FILE *f) { +wchar_t *fgetws_unlocked(wchar_t *s, int size, FILE *f) { wint_t c; wchar_t *p = s; if (size > 0) { while (--size > 0) { - if ((c = fgetwc(f)) == -1) { - if (ferror(f) == EINTR) continue; + if ((c = fgetwc_unlocked(f)) == -1) { + if (ferror_unlocked(f) == EINTR) continue; break; } *p++ = c; diff --git a/libc/stdio/fileno.c b/libc/stdio/fileno.c index a16d85890..252c02863 100644 --- a/libc/stdio/fileno.c +++ b/libc/stdio/fileno.c @@ -22,7 +22,7 @@ /** * Returns file descriptor associated with stream. */ -int fileno(FILE *f) { +int fileno_unlocked(FILE *f) { if (f->fd != -1) { return f->fd; } else { diff --git a/libc/stdio/flockfile.c b/libc/stdio/flockfile.c index fd6e8e6ad..5ffe0c691 100644 --- a/libc/stdio/flockfile.c +++ b/libc/stdio/flockfile.c @@ -16,23 +16,12 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/spinlock.h" #include "libc/stdio/stdio.h" /** - * Does nothing since Cosmopolitan currently doesn't support threads. + * Acquires lock on stdio object, blocking if needed. */ void flockfile(FILE *f) { -} - -/** - * Does nothing since Cosmopolitan currently doesn't support threads. - */ -void funlockfile(FILE *f) { -} - -/** - * Does nothing since Cosmopolitan currently doesn't support threads. - */ -int ftrylockfile(FILE *f) { - return 0; + _spinlock(&f->lock); } diff --git a/libc/stdio/flushlbf.c b/libc/stdio/flushlbf.c index a714b2559..bacc56caa 100644 --- a/libc/stdio/flushlbf.c +++ b/libc/stdio/flushlbf.c @@ -17,7 +17,9 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/intrin/spinlock.h" #include "libc/stdio/fflush.internal.h" +#include "libc/stdio/stdio.h" #include "libc/stdio/stdio_ext.h" /** @@ -25,9 +27,16 @@ */ void _flushlbf(void) { int i; + FILE *f; + _spinlock(&__fflush.lock); for (i = 0; i < __fflush.handles.i; ++i) { - if (__fflush.handles.p[i]->bufmode == _IOLBF) { - fflush(__fflush.handles.p[i]); + if ((f = __fflush.handles.p[i])) { + _spinlock(&f->lock); + if (f->bufmode == _IOLBF) { + fflush_unlocked(f); + } + _spunlock(&f->lock); } } + _spunlock(&__fflush.lock); } diff --git a/libc/stdio/fputc.c b/libc/stdio/fputc.c index 635294fa5..9b432718d 100644 --- a/libc/stdio/fputc.c +++ b/libc/stdio/fputc.c @@ -25,14 +25,14 @@ * @param c is byte to buffer or write, which is masked * @return c as unsigned char if written or -1 w/ errno */ -int fputc(int c, FILE *f) { +int fputc_unlocked(int c, FILE *f) { unsigned char b; if (c != '\n' && f->beg < f->size && f->bufmode != _IONBF) { f->buf[f->beg++] = c; return c & 0xff; } else { b = c; - if (!fwrite(&b, 1, 1, f)) return -1; + if (!fwrite_unlocked(&b, 1, 1, f)) return -1; return b; } } diff --git a/libc/stdio/fputs.c b/libc/stdio/fputs.c index 149b03c5c..7e58ed189 100644 --- a/libc/stdio/fputs.c +++ b/libc/stdio/fputs.c @@ -30,10 +30,10 @@ * @param f is an open stream * @return bytes written, or -1 w/ errno */ -int fputs(const char *s, FILE *f) { +int fputs_unlocked(const char *s, FILE *f) { size_t n, r; n = strlen(s); - r = fwrite(s, 1, n, f); + r = fwrite_unlocked(s, 1, n, f); if (!r && n) return -1; return r; } diff --git a/libc/stdio/fputwc.c b/libc/stdio/fputwc.c index 3bb23e72b..29f5d7716 100644 --- a/libc/stdio/fputwc.c +++ b/libc/stdio/fputwc.c @@ -24,12 +24,12 @@ * * @return wc if written or -1 w/ errno */ -wint_t fputwc(wchar_t wc, FILE *f) { +wint_t fputwc_unlocked(wchar_t wc, FILE *f) { uint64_t w; if (wc != -1) { w = tpenc(wc); do { - if (fputc(w, f) == -1) { + if (fputc_unlocked(w, f) == -1) { return -1; } } while ((w >>= 8)); diff --git a/libc/stdio/fputws.c b/libc/stdio/fputws.c index b31fb1782..e5fca98fa 100644 --- a/libc/stdio/fputws.c +++ b/libc/stdio/fputws.c @@ -30,12 +30,16 @@ * @param f is an open stream * @return strlen(s) or -1 w/ errno on error */ -int fputws(const wchar_t *s, FILE *f) { +int fputws_unlocked(const wchar_t *s, FILE *f) { int res = 0; while (*s) { - if (fputwc(*s++, f) == -1) { - if (ferror(f) == EINTR) continue; - if (feof(f)) errno = f->state = EPIPE; + if (fputwc_unlocked(*s++, f) == -1) { + if (ferror_unlocked(f) == EINTR) { + continue; + } + if (feof_unlocked(f)) { + errno = f->state = EPIPE; + } return -1; } ++res; diff --git a/libc/stdio/fread.c b/libc/stdio/fread.c index 4b47c862a..6c7f195c9 100644 --- a/libc/stdio/fread.c +++ b/libc/stdio/fread.c @@ -37,7 +37,7 @@ * @param count is the number of strides to fetch * @return count on success, [0,count) on eof, or 0 on error or count==0 */ -size_t fread(void *buf, size_t stride, size_t count, FILE *f) { +size_t fread_unlocked(void *buf, size_t stride, size_t count, FILE *f) { char *p; ssize_t rc; size_t n, m; diff --git a/libc/stdio/freopen.c b/libc/stdio/freopen.c index 2f83742d3..4d5a4218c 100644 --- a/libc/stdio/freopen.c +++ b/libc/stdio/freopen.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/intrin/spinlock.h" #include "libc/stdio/stdio.h" #include "libc/sysv/consts/f.h" #include "libc/sysv/consts/fd.h" @@ -37,9 +38,11 @@ */ FILE *freopen(const char *pathname, const char *mode, FILE *stream) { int fd; + FILE *res; unsigned flags; flags = fopenflags(mode); - fflush(stream); + _spinlock(&stream->lock); + fflush_unlocked(stream); if (pathname) { /* open new stream, overwriting existing alloc */ if ((fd = open(pathname, flags, 0666)) != -1) { @@ -48,13 +51,15 @@ FILE *freopen(const char *pathname, const char *mode, FILE *stream) { stream->iomode = flags; stream->beg = 0; stream->end = 0; - return stream; + res = stream; } else { - return NULL; + res = NULL; } } else { fcntl(stream->fd, F_SETFD, !!(flags & O_CLOEXEC)); fcntl(stream->fd, F_SETFL, flags & ~O_CLOEXEC); - return stream; + res = stream; } + _spunlock(&stream->lock); + return res; } diff --git a/libc/stdio/fseeko.c b/libc/stdio/fseeko.c index 8e30f72a9..2248dffae 100644 --- a/libc/stdio/fseeko.c +++ b/libc/stdio/fseeko.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/errno.h" +#include "libc/intrin/spinlock.h" #include "libc/stdio/internal.h" #include "libc/stdio/stdio.h" #include "libc/sysv/consts/o.h" @@ -36,8 +37,10 @@ * @returns 0 on success or -1 on error */ int fseeko(FILE *f, int64_t offset, int whence) { + int res; ssize_t rc; int64_t pos; + _spinlock(&f->lock); if (f->fd != -1) { if (__fflush_impl(f) == -1) return -1; if (whence == SEEK_CUR && f->beg < f->end) { @@ -46,10 +49,10 @@ int fseeko(FILE *f, int64_t offset, int whence) { if (lseek(f->fd, offset, whence) != -1) { f->beg = 0; f->end = 0; - return 0; + res = 0; } else { f->state = errno == ESPIPE ? EBADF : errno; - return -1; + res = -1; } } else { switch (whence) { @@ -68,10 +71,12 @@ int fseeko(FILE *f, int64_t offset, int whence) { } if (0 <= pos && pos <= f->end) { f->beg = pos; - return 0; + res = 0; } else { f->state = errno = EINVAL; - return -1; + res = -1; } } + _spunlock(&f->lock); + return res; } diff --git a/libc/stdio/fsetlocking.c b/libc/stdio/fsetlocking.c index 2dd880e79..b20527e6c 100644 --- a/libc/stdio/fsetlocking.c +++ b/libc/stdio/fsetlocking.c @@ -19,8 +19,8 @@ #include "libc/stdio/stdio_ext.h" /** - * Does nothing and returns `FSETLOCKING_BYCALLER`. + * Does nothing and returns `FSETLOCKING_INTERNAL`. */ int __fsetlocking(FILE *f, int type) { - return FSETLOCKING_BYCALLER; + return FSETLOCKING_INTERNAL; } diff --git a/libc/stdio/ftello.c b/libc/stdio/ftello.c index 2b25070cf..d3b88c846 100644 --- a/libc/stdio/ftello.c +++ b/libc/stdio/ftello.c @@ -18,18 +18,13 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/errno.h" +#include "libc/intrin/spinlock.h" #include "libc/runtime/runtime.h" #include "libc/stdio/internal.h" #include "libc/stdio/stdio.h" #include "libc/sysv/consts/o.h" -/** - * Returns current position of stream. - * - * @param stream is a non-null stream handle - * @returns current byte offset from beginning, or -1 w/ errno - */ -int64_t ftello(FILE *f) { +static int64_t ftello_unlocked(FILE *f) { int64_t pos; uint32_t skew; if (f->fd != -1) { @@ -45,3 +40,17 @@ int64_t ftello(FILE *f) { return f->beg; } } + +/** + * Returns current position of stream. + * + * @param stream is a non-null stream handle + * @returns current byte offset from beginning, or -1 w/ errno + */ +int64_t ftello(FILE *f) { + int64_t rc; + _spinlock(&f->lock); + rc = ftello_unlocked(f); + _spunlock(&f->lock); + return rc; +} diff --git a/libc/stdio/ftrylockfile.c b/libc/stdio/ftrylockfile.c new file mode 100644 index 000000000..97d21da2f --- /dev/null +++ b/libc/stdio/ftrylockfile.c @@ -0,0 +1,28 @@ +/*-*- 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/spinlock.h" +#include "libc/stdio/stdio.h" + +/** + * Tries to acquire stdio object lock. + * @return 0 for success or non-zero if someone else has the lock + */ +int ftrylockfile(FILE *f) { + return _trylock(&f->lock); +} diff --git a/libc/stdio/funlockfile.c b/libc/stdio/funlockfile.c new file mode 100644 index 000000000..46d68d0fd --- /dev/null +++ b/libc/stdio/funlockfile.c @@ -0,0 +1,27 @@ +/*-*- 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/spinlock.h" +#include "libc/stdio/stdio.h" + +/** + * Releases lock on stdio object. + */ +void funlockfile(FILE *f) { + _spunlock(&f->lock); +} diff --git a/libc/stdio/fwrite.c b/libc/stdio/fwrite.c index 395f0d41f..e7317f343 100644 --- a/libc/stdio/fwrite.c +++ b/libc/stdio/fwrite.c @@ -36,7 +36,7 @@ * @param count is the number of strides to write * @return count on success, [0,count) on EOF, 0 on error or count==0 */ -size_t fwrite(const void *data, size_t stride, size_t count, FILE *f) { +size_t fwrite_unlocked(const void *data, size_t stride, size_t count, FILE *f) { ldiv_t d; ssize_t rc; size_t n, m; diff --git a/libc/stdio/getc.c b/libc/stdio/getc.c index 71082bce8..8f4ea49c2 100644 --- a/libc/stdio/getc.c +++ b/libc/stdio/getc.c @@ -22,6 +22,6 @@ * Reads byte from stream. * @return byte in range 0..255, or -1 w/ errno */ -int(getc)(FILE *f) { - return fgetc(f); +int(getc_unlocked)(FILE *f) { + return fgetc_unlocked(f); } diff --git a/libc/stdio/getchar.c b/libc/stdio/getchar.c index db56d6b05..b7c1b9f58 100644 --- a/libc/stdio/getchar.c +++ b/libc/stdio/getchar.c @@ -19,9 +19,9 @@ #include "libc/stdio/stdio.h" /** - * Reads byte from stream. + * Reads byte from stdin. * @return byte in range 0..255, or -1 w/ errno */ -int getchar(void) { - return fgetc(stdin); +int getchar_unlocked(void) { + return fgetc_unlocked(stdin); } diff --git a/libc/stdio/getdelim.c b/libc/stdio/getdelim.c index d936d05d3..ae2a91f95 100644 --- a/libc/stdio/getdelim.c +++ b/libc/stdio/getdelim.c @@ -19,6 +19,7 @@ #include "libc/assert.h" #include "libc/calls/calls.h" #include "libc/errno.h" +#include "libc/intrin/spinlock.h" #include "libc/macros.internal.h" #include "libc/mem/mem.h" #include "libc/runtime/runtime.h" @@ -26,19 +27,7 @@ #include "libc/str/str.h" #include "libc/sysv/consts/o.h" -/** - * Reads string from stream. - * - * @param s is the caller's buffer (in/out) which is extended or - * allocated automatically, also NUL-terminated is guaranteed - * @param n is the capacity of s (in/out) - * @param delim is the stop char (and NUL is implicitly too) - * @return number of bytes read >0, including delim, excluding NUL, - * or -1 w/ errno on EOF or error; see ferror() and feof() - * @note this function can't punt EINTR to caller - * @see getline(), _chomp(), gettok_r() - */ -ssize_t getdelim(char **s, size_t *n, int delim, FILE *f) { +static ssize_t getdelim_unlocked(char **s, size_t *n, int delim, FILE *f) { char *p; ssize_t rc; size_t i, m; @@ -83,3 +72,23 @@ ssize_t getdelim(char **s, size_t *n, int delim, FILE *f) { return -1; } } + +/** + * Reads string from stream. + * + * @param s is the caller's buffer (in/out) which is extended or + * allocated automatically, also NUL-terminated is guaranteed + * @param n is the capacity of s (in/out) + * @param delim is the stop char (and NUL is implicitly too) + * @return number of bytes read >0, including delim, excluding NUL, + * or -1 w/ errno on EOF or error; see ferror() and feof() + * @note this function can't punt EINTR to caller + * @see getline(), _chomp(), gettok_r() + */ +ssize_t getdelim(char **s, size_t *n, int delim, FILE *f) { + ssize_t rc; + _spinlock(&f->lock); + rc = getdelim_unlocked(s, n, delim, f); + _spunlock(&f->lock); + return rc; +} diff --git a/libc/stdio/getwc.c b/libc/stdio/getwc.c index 2ec4cc1bf..0bb9975e6 100644 --- a/libc/stdio/getwc.c +++ b/libc/stdio/getwc.c @@ -22,6 +22,6 @@ * Reads UTF-8 character from stream. * @return wide character or -1 on EOF or error */ -wint_t(getwc)(FILE *f) { - return fgetwc(f); +wint_t(getwc_unlocked)(FILE *f) { + return fgetwc_unlocked(f); } diff --git a/libc/stdio/getwchar.c b/libc/stdio/getwchar.c index a455dbc11..dcfd7ceb3 100644 --- a/libc/stdio/getwchar.c +++ b/libc/stdio/getwchar.c @@ -22,6 +22,6 @@ * Reads UTF-8 character from stream. * @return wide character or -1 on EOF or error */ -wint_t getwchar(void) { - return fgetwc(stdin); +wint_t getwchar_unlocked(void) { + return fgetwc_unlocked(stdin); } diff --git a/libc/stdio/putc.c b/libc/stdio/putc.c index 559519a32..a587d245d 100644 --- a/libc/stdio/putc.c +++ b/libc/stdio/putc.c @@ -24,6 +24,6 @@ * @param c is byte to buffer or write, which is masked * @return c as unsigned char if written or -1 w/ errno */ -int(putc)(int c, FILE *f) { - return fputc(c, f); +int(putc_unlocked)(int c, FILE *f) { + return fputc_unlocked(c, f); } diff --git a/libc/stdio/putchar.c b/libc/stdio/putchar.c index 55b7c3c2c..3c247cc3b 100644 --- a/libc/stdio/putchar.c +++ b/libc/stdio/putchar.c @@ -23,4 +23,6 @@ * * @return c (as unsigned char) if written or -1 w/ errno */ -int putchar(int c) { return fputc(c, stdout); } +int putchar_unlocked(int c) { + return fputc_unlocked(c, stdout); +} diff --git a/libc/stdio/putwc.c b/libc/stdio/putwc.c index fad85cadb..361a0cca6 100644 --- a/libc/stdio/putwc.c +++ b/libc/stdio/putwc.c @@ -23,6 +23,6 @@ * * @return wc if written or -1 w/ errno */ -wint_t(putwc)(wchar_t wc, FILE *f) { - return fputwc(wc, f); +wint_t(putwc_unlocked)(wchar_t wc, FILE *f) { + return fputwc_unlocked(wc, f); } diff --git a/libc/stdio/putwchar.c b/libc/stdio/putwchar.c index 8fe5cf01c..2565c2c0e 100644 --- a/libc/stdio/putwchar.c +++ b/libc/stdio/putwchar.c @@ -22,6 +22,6 @@ * Writes wide character to stdout. * @return wc if written or -1 w/ errno */ -wint_t putwchar(wchar_t wc) { - return fputwc(wc, stdout); +wint_t putwchar_unlocked(wchar_t wc) { + return fputwc_unlocked(wc, stdout); } diff --git a/libc/stdio/stdio.h b/libc/stdio/stdio.h index c869a3774..6c0914db8 100644 --- a/libc/stdio/stdio.h +++ b/libc/stdio/stdio.h @@ -24,7 +24,8 @@ typedef struct FILE { uint32_t size; /* 0x20 */ uint32_t nofree; /* 0x24 */ int pid; /* 0x28 */ - char *getln; + char lock; /* 0x2c */ + char *getln; /* 0x30 */ } FILE; extern FILE *stdin; @@ -135,11 +136,48 @@ int fwide(FILE *, int); #define vfscanf(F, FMT, VA) (vfscanf)(F, SFLINK(FMT), VA) #endif -COSMOPOLITAN_C_END_ -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ - #define stdin SYMBOLIC(stdin) #define stdout SYMBOLIC(stdout) #define stderr SYMBOLIC(stderr) +/*───────────────────────────────────────────────────────────────────────────│─╗ +│ cosmopolitan § standard i/o » without mutexes ─╬─│┼ +╚────────────────────────────────────────────────────────────────────────────│*/ + +void flockfile(FILE *); +void funlockfile(FILE *); +int ftrylockfile(FILE *); +int getc_unlocked(FILE *) paramsnonnull(); +int getchar_unlocked(void); +int putc_unlocked(int, FILE *) paramsnonnull(); +int putchar_unlocked(int); +void clearerr_unlocked(FILE *); +int feof_unlocked(FILE *); +int ferror_unlocked(FILE *); +int fileno_unlocked(FILE *); +int fflush_unlocked(FILE *); +int fgetc_unlocked(FILE *); +int fputc_unlocked(int, FILE *); +size_t fread_unlocked(void *, size_t, size_t, FILE *); +size_t fwrite_unlocked(const void *, size_t, size_t, FILE *); +char *fgets_unlocked(char *, int, FILE *); +int fputs_unlocked(const char *, FILE *); +wint_t getwc_unlocked(FILE *); +wint_t getwchar_unlocked(void); +wint_t fgetwc_unlocked(FILE *); +wint_t fputwc_unlocked(wchar_t, FILE *); +wint_t putwc_unlocked(wchar_t, FILE *); +wint_t putwchar_unlocked(wchar_t); +wchar_t *fgetws_unlocked(wchar_t *, int, FILE *); +int fputws_unlocked(const wchar_t *, FILE *); +wint_t ungetwc_unlocked(wint_t, FILE *) paramsnonnull(); +int ungetc_unlocked(int, FILE *) paramsnonnull(); + +#define getc_unlocked(f) fgetc_unlocked(f) +#define getwc_unlocked(f) fgetwc_unlocked(f) +#define putc_unlocked(c, f) fputc_unlocked(c, f) +#define putwc_unlocked(c, f) fputwc_unlocked(c, f) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_STDIO_STDIO_H_ */ diff --git a/libc/stdio/ungetc.c b/libc/stdio/ungetc.c index 91a28e22a..975ebdffb 100644 --- a/libc/stdio/ungetc.c +++ b/libc/stdio/ungetc.c @@ -22,7 +22,7 @@ /** * Pushes byte back to stream. */ -int ungetc(int c, FILE *f) { +int ungetc_unlocked(int c, FILE *f) { if (c == -1) return -1; if (f->beg) { f->buf[--f->beg] = c; @@ -32,5 +32,5 @@ int ungetc(int c, FILE *f) { } else { return -1; } - return c & 0xff; + return c & 255; } diff --git a/libc/stdio/ungetwc.c b/libc/stdio/ungetwc.c index be1fce4f6..2673e0526 100644 --- a/libc/stdio/ungetwc.c +++ b/libc/stdio/ungetwc.c @@ -23,7 +23,7 @@ /** * Pushes wide character back to stream. */ -wint_t ungetwc(wint_t c, FILE *f) { +wint_t ungetwc_unlocked(wint_t c, FILE *f) { char b[6]; unsigned n; uint64_t w; diff --git a/libc/stdio/unlocked.h b/libc/stdio/unlocked.h deleted file mode 100644 index 868646671..000000000 --- a/libc/stdio/unlocked.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_STDIO_UNLOCKED_H_ -#define COSMOPOLITAN_LIBC_STDIO_UNLOCKED_H_ -#include "libc/stdio/stdio.h" -#if !(__ASSEMBLER__ + __LINKER__ + 0) -COSMOPOLITAN_C_START_ - -void flockfile(FILE *); -void funlockfile(FILE *); -int ftrylockfile(FILE *); -int getc_unlocked(FILE *) paramsnonnull(); -int getchar_unlocked(void); -int putc_unlocked(int, FILE *) paramsnonnull(); -int putchar_unlocked(int); -void clearerr_unlocked(FILE *); -int feof_unlocked(FILE *); -int ferror_unlocked(FILE *); -int fileno_unlocked(FILE *); -int fflush_unlocked(FILE *); -int fgetc_unlocked(FILE *); -int fputc_unlocked(int, FILE *); -size_t fread_unlocked(void *, size_t, size_t, FILE *); -size_t fwrite_unlocked(const void *, size_t, size_t, FILE *); -char *fgets_unlocked(char *, int, FILE *); -int fputs_unlocked(const char *, FILE *); -wint_t getwc_unlocked(FILE *); -wint_t getwchar_unlocked(void); -wint_t fgetwc_unlocked(FILE *); -wint_t fputwc_unlocked(wchar_t, FILE *); -wint_t putwc_unlocked(wchar_t, FILE *); -wint_t putwchar_unlocked(wchar_t); -wchar_t *fgetws_unlocked(wchar_t *, int, FILE *); -int fputws_unlocked(const wchar_t *, FILE *); - -COSMOPOLITAN_C_END_ -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ -#endif /* COSMOPOLITAN_LIBC_STDIO_UNLOCKED_H_ */ diff --git a/libc/stdio/unlocked/clearerr_unlocked.S b/libc/stdio/unlocked/clearerr_unlocked.S index 49710de62..b62225c26 100644 --- a/libc/stdio/unlocked/clearerr_unlocked.S +++ b/libc/stdio/unlocked/clearerr_unlocked.S @@ -18,11 +18,12 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -clearerr_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call clearerr - pop %rbp - ret - .endfn clearerr_unlocked,globl +// Clears error state on stream. +// +// @param rdi has stream pointer +// @see clearerr_unlocked() +clearerr: + mov %rdi,%r11 + ezlea clearerr_unlocked,ax + jmp stdio_unlock + .endfn clearerr,globl diff --git a/libc/stdio/unlocked/feof_unlocked.S b/libc/stdio/unlocked/feof_unlocked.S index 6b89b623a..a498c5359 100644 --- a/libc/stdio/unlocked/feof_unlocked.S +++ b/libc/stdio/unlocked/feof_unlocked.S @@ -18,11 +18,12 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -feof_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call feof - pop %rbp - ret - .endfn feof_unlocked,globl +// Returns true if stream is in end-of-file state. +// +// @param rdi has file stream object pointer +// @note EOF doesn't count +// @see feof_unlocked() +feof: mov %rdi,%r11 + ezlea feof_unlocked,ax + jmp stdio_unlock + .endfn feof,globl diff --git a/libc/stdio/unlocked/ferror_unlocked.S b/libc/stdio/unlocked/ferror_unlocked.S index 8d0df702a..3c5fb28f4 100644 --- a/libc/stdio/unlocked/ferror_unlocked.S +++ b/libc/stdio/unlocked/ferror_unlocked.S @@ -18,11 +18,12 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -ferror_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call ferror - pop %rbp - ret - .endfn ferror_unlocked,globl +// Returns nonzero if stream is in error state. +// +// @param rdi has file stream object pointer +// @note EOF doesn't count +// @see ferror_unlocked() +ferror: mov %rdi,%r11 + ezlea ferror_unlocked,ax + jmp stdio_unlock + .endfn ferror,globl diff --git a/libc/stdio/unlocked/fflush_unlocked.S b/libc/stdio/unlocked/fflush_unlocked.S index 982aeb84d..c91963f1c 100644 --- a/libc/stdio/unlocked/fflush_unlocked.S +++ b/libc/stdio/unlocked/fflush_unlocked.S @@ -18,11 +18,12 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -fflush_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call fflush - pop %rbp - ret - .endfn fflush_unlocked,globl +// Blocks until data from stream buffer is written out. +// +// @param rdi is the stream handle +// @return 0 on success or -1 w/ errno +// @see fflush_unlocked() +fflush: mov %rdi,%r11 + ezlea fflush_unlocked,ax + jmp stdio_unlock + .endfn fflush,globl diff --git a/libc/stdio/unlocked/fgetc_unlocked.S b/libc/stdio/unlocked/fgetc_unlocked.S index 0d6f97f34..59f250529 100644 --- a/libc/stdio/unlocked/fgetc_unlocked.S +++ b/libc/stdio/unlocked/fgetc_unlocked.S @@ -18,11 +18,12 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -fgetc_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call fgetc - pop %rbp - ret - .endfn fgetc_unlocked,globl +// Reads byte from stream. +// +// @param rdi has stream object pointer +// @return byte in range 0..255, or -1 w/ errno +// @see fgetc_unlocked() +fgetc: mov %rdi,%r11 + ezlea fgetc_unlocked,ax + jmp stdio_unlock + .endfn fgetc,globl diff --git a/libc/stdio/unlocked/fgets_unlocked.S b/libc/stdio/unlocked/fgets_unlocked.S index 424362a2a..8b155e990 100644 --- a/libc/stdio/unlocked/fgets_unlocked.S +++ b/libc/stdio/unlocked/fgets_unlocked.S @@ -18,11 +18,19 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -fgets_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call fgets - pop %rbp - ret - .endfn fgets_unlocked,globl +// Reads line from stream. +// +// This function is similar to getline() except it'll truncate +// lines exceeding size. The line ending marker is included +// and may be removed using _chomp(). +// +// @param rdi is output buffer +// @param rsi is size of rdi buffer +// @param rdx is file stream object pointer +// @return rax has rdi on success, NULL on error or +// NULL if EOF happens with zero chars read +// @see fgets_unlocked() +fgets: mov %rdx,%r11 + ezlea fgets_unlocked,ax + jmp stdio_unlock + .endfn fgets,globl diff --git a/libc/stdio/unlocked/fgetwc_unlocked.S b/libc/stdio/unlocked/fgetwc_unlocked.S index 7a658dc3a..667a67756 100644 --- a/libc/stdio/unlocked/fgetwc_unlocked.S +++ b/libc/stdio/unlocked/fgetwc_unlocked.S @@ -18,11 +18,12 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -fgetwc_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call fgetwc - pop %rbp - ret - .endfn fgetwc_unlocked,globl +// Reads UTF-8 wide character from stream. +// +// @param rdi has stream object pointer +// @return wide character or -1 on EOF or error +// @see fgetwc_unlocked() +fgetwc: mov %rdi,%r11 + ezlea fgetwc_unlocked,ax + jmp stdio_unlock + .endfn fgetwc,globl diff --git a/libc/stdio/unlocked/fgetws_unlocked.S b/libc/stdio/unlocked/fgetws_unlocked.S index 6c8a377e1..1057725c0 100644 --- a/libc/stdio/unlocked/fgetws_unlocked.S +++ b/libc/stdio/unlocked/fgetws_unlocked.S @@ -18,11 +18,17 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -fgetws_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call fgetws - pop %rbp - ret - .endfn fgetws_unlocked,globl +// Reads UTF-8 content from stream into UTF-32 buffer. +// +// This function is similar to getline() except it'll truncate lines +// exceeding size. The line ending marker is included and may be removed +// using _chomp(). +// +// @param rdi is nul-terminated string that's non-null +// @param rsi is size of rdi buffer +// @param rsi is file stream object pointer +// @see fgetws_unlocked() +fgetws: mov %rdx,%r11 + ezlea fgetws_unlocked,ax + jmp stdio_unlock + .endfn fgetws,globl diff --git a/libc/stdio/unlocked/fileno_unlocked.S b/libc/stdio/unlocked/fileno_unlocked.S index 2f9d0b775..d8804a18e 100644 --- a/libc/stdio/unlocked/fileno_unlocked.S +++ b/libc/stdio/unlocked/fileno_unlocked.S @@ -18,11 +18,11 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -fileno_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call fileno - pop %rbp - ret - .endfn fileno_unlocked,globl +// Returns file descriptor associated with stream. +// +// @param rdi has file stream object pointer +// @see fileno_unlocked() +fileno: mov %rdi,%r11 + ezlea fileno_unlocked,ax + jmp stdio_unlock + .endfn fileno,globl diff --git a/libc/stdio/unlocked/fputc_unlocked.S b/libc/stdio/unlocked/fputc_unlocked.S index 152b949e8..a51feb9bc 100644 --- a/libc/stdio/unlocked/fputc_unlocked.S +++ b/libc/stdio/unlocked/fputc_unlocked.S @@ -18,11 +18,13 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -fputc_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call fputc - pop %rbp - ret - .endfn fputc_unlocked,globl +// Writes character to stream. +// +// @param rdi c is byte to buffer or write, which is masked +// @param rsi has stream object pointer +// @return c as unsigned char if written or -1 w/ errno +// @see fputc_unlocked() +fputc: mov %rsi,%r11 + ezlea fputc_unlocked,ax + jmp stdio_unlock + .endfn fputc,globl diff --git a/libc/stdio/unlocked/fputs_unlocked.S b/libc/stdio/unlocked/fputs_unlocked.S index 9c841a471..1a2cdfa2a 100644 --- a/libc/stdio/unlocked/fputs_unlocked.S +++ b/libc/stdio/unlocked/fputs_unlocked.S @@ -18,11 +18,17 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -fputs_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call fputs - pop %rbp - ret - .endfn fputs_unlocked,globl +// Writes string to stream. +// +// Writing stops at the NUL-terminator, which isn't included in output. +// This function blocks until the full string is written, unless an +// unrecoverable error happens. +// +// @param rdi is nul-terminated string that's non-null +// @param rsi is file object stream pointer +// @return strlen(rdi) on success or -1 w/ errno +// @see fputs_unlocked() +fputs: mov %rsi,%r11 + ezlea fputs_unlocked,ax + jmp stdio_unlock + .endfn fputs,globl diff --git a/libc/stdio/unlocked/fputwc_unlocked.S b/libc/stdio/unlocked/fputwc_unlocked.S index 58d33dbc5..0c140da48 100644 --- a/libc/stdio/unlocked/fputwc_unlocked.S +++ b/libc/stdio/unlocked/fputwc_unlocked.S @@ -18,11 +18,13 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -fputwc_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call fputwc - pop %rbp - ret - .endfn fputwc_unlocked,globl +// Writes wide character to stream. +// +// @param rdi has wide character +// @param rsi has file object stream pointer +// @return rax is wide character if written or -1 w/ errno +// @see fputwc_unlocked() +fputwc: mov %rsi,%r11 + ezlea fputwc_unlocked,ax + jmp stdio_unlock + .endfn fputwc,globl diff --git a/libc/stdio/unlocked/fputws_unlocked.S b/libc/stdio/unlocked/fputws_unlocked.S index 238489ed5..3258d4a7f 100644 --- a/libc/stdio/unlocked/fputws_unlocked.S +++ b/libc/stdio/unlocked/fputws_unlocked.S @@ -18,11 +18,17 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -fputws_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call fputws - pop %rbp - ret - .endfn fputws_unlocked,globl +// Writes wide character string to stream. +// +// Writing stops at the NUL-terminator, which isn't included in output. +// This function blocks until the full string is written, unless an +// unrecoverable error happens. +// +// @param rdi is nul-terminated string that's non-null +// @param rsi is file object stream pointer +// @return strlen(rdi) on success or -1 w/ errno +// @see fputws_unlocked() +fputws: mov %rsi,%r11 + ezlea fputws_unlocked,ax + jmp stdio_unlock + .endfn fputws,globl diff --git a/libc/stdio/unlocked/fread_unlocked.S b/libc/stdio/unlocked/fread_unlocked.S index 84c2407f2..e8084b9b8 100644 --- a/libc/stdio/unlocked/fread_unlocked.S +++ b/libc/stdio/unlocked/fread_unlocked.S @@ -18,11 +18,15 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -fread_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call fread - pop %rbp - ret - .endfn fread_unlocked,globl +// Reads data from stream. +// +// @param rdi has pointer to data to read +// @param rsi stride specifies the size of individual items +// @param rdx count is the number of strides to read +// @param rcx has file object stream pointer +// @return count on success, [0,count) on EOF, 0 on error or count==0 +// @see fread_unlocked() +fread: mov %rcx,%r11 + ezlea fread_unlocked,ax + jmp stdio_unlock + .endfn fread,globl diff --git a/libc/stdio/unlocked/fwrite_unlocked.S b/libc/stdio/unlocked/fwrite_unlocked.S index ca5fd20f4..7ea005850 100644 --- a/libc/stdio/unlocked/fwrite_unlocked.S +++ b/libc/stdio/unlocked/fwrite_unlocked.S @@ -18,11 +18,15 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -fwrite_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call fwrite - pop %rbp - ret - .endfn fwrite_unlocked,globl +// Writes data to stream. +// +// @param rdi has pointer to data to write +// @param rsi stride specifies the size of individual items +// @param rdx count is the number of strides to write +// @param rcx has file object stream pointer +// @return count on success, [0,count) on EOF, 0 on error or count==0 +// @see fwrite_unlocked() +fwrite: mov %rcx,%r11 + ezlea fwrite_unlocked,ax + jmp stdio_unlock + .endfn fwrite,globl diff --git a/libc/stdio/unlocked/getc_unlocked.S b/libc/stdio/unlocked/getc_unlocked.S index 488d48a93..0d06c5218 100644 --- a/libc/stdio/unlocked/getc_unlocked.S +++ b/libc/stdio/unlocked/getc_unlocked.S @@ -18,11 +18,12 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -getc_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call getc - pop %rbp - ret - .endfn getc_unlocked,globl +// Reads character from stream. +// +// @param rdi has file stream object pointer +// @return byte in range 0..255, or -1 w/ errno +// @see fgetc_unlocked() +getc: mov %rdi,%r11 + ezlea fgetwc_unlocked,ax + jmp stdio_unlock + .endfn getc,globl diff --git a/libc/stdio/unlocked/getchar_unlocked.S b/libc/stdio/unlocked/getchar_unlocked.S index ff7285685..5bc14faaa 100644 --- a/libc/stdio/unlocked/getchar_unlocked.S +++ b/libc/stdio/unlocked/getchar_unlocked.S @@ -18,11 +18,13 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -getchar_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call getchar - pop %rbp - ret - .endfn getchar_unlocked,globl +// Reads character from stdin. +// +// @return byte in range 0..255, or -1 w/ errno +// @see fgetc_unlocked() +getchar: + lea stdin(%rip),%rdi + mov %rdi,%r11 + ezlea fgetc_unlocked,ax + jmp stdio_unlock + .endfn getchar,globl diff --git a/libc/stdio/unlocked/getwc_unlocked.S b/libc/stdio/unlocked/getwc_unlocked.S index 1c06b74a3..c4eca5ee2 100644 --- a/libc/stdio/unlocked/getwc_unlocked.S +++ b/libc/stdio/unlocked/getwc_unlocked.S @@ -18,11 +18,12 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -getwc_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call fgetwc_unlocked - pop %rbp - ret - .endfn getwc_unlocked,globl +// Reads UTF-8 character from stream. +// +// @param rdi has file stream object pointer +// @return wide character or -1 on EOF or error +// @see fgetwc_unlocked() +getwc: mov %rdi,%r11 + ezlea fgetwc_unlocked,ax + jmp stdio_unlock + .endfn getwc,globl diff --git a/libc/stdio/unlocked/getwchar_unlocked.S b/libc/stdio/unlocked/getwchar_unlocked.S index 07acd3314..f76f34285 100644 --- a/libc/stdio/unlocked/getwchar_unlocked.S +++ b/libc/stdio/unlocked/getwchar_unlocked.S @@ -18,11 +18,13 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -getwchar_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call getwchar - pop %rbp - ret - .endfn getwchar_unlocked,globl +// Reads UTF-8 character from stdin. +// +// @return wide character or -1 on EOF or error +// @see fgetwc_unlocked() +getwchar: + lea stdin(%rip),%rdi + mov %rdi,%r11 + ezlea fgetwc_unlocked,ax + jmp stdio_unlock + .endfn getwchar,globl diff --git a/libc/stdio/unlocked/putc_unlocked.S b/libc/stdio/unlocked/putc_unlocked.S index 7784877a7..fd5da3886 100644 --- a/libc/stdio/unlocked/putc_unlocked.S +++ b/libc/stdio/unlocked/putc_unlocked.S @@ -18,11 +18,13 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -putc_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call putc - pop %rbp - ret - .endfn putc_unlocked,globl +// Writes character to stream. +// +// @param rdi c is byte to buffer or write, which is masked +// @param rsi has stream object pointer +// @return c as unsigned char if written or -1 w/ errno +// @see fputc_unlocked() +putc: mov %rsi,%r11 + ezlea fputc_unlocked,ax + jmp stdio_unlock + .endfn putc,globl diff --git a/libc/stdio/unlocked/putchar_unlocked.S b/libc/stdio/unlocked/putchar_unlocked.S index e7ffcb38e..b1d55fab7 100644 --- a/libc/stdio/unlocked/putchar_unlocked.S +++ b/libc/stdio/unlocked/putchar_unlocked.S @@ -18,11 +18,14 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -putchar_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call putchar - pop %rbp - ret - .endfn putchar_unlocked,globl +// Writes character to stdout. +// +// @param rdi has character +// @return c (as unsigned char) if written or -1 w/ errno +// @see fputc_unlocked() +putchar: + lea stdout(%rip),%rsi + mov %rsi,%r11 + ezlea fputc_unlocked,ax + jmp stdio_unlock + .endfn putchar,globl diff --git a/libc/stdio/unlocked/putwc_unlocked.S b/libc/stdio/unlocked/putwc_unlocked.S index d12280e00..d66a91efe 100644 --- a/libc/stdio/unlocked/putwc_unlocked.S +++ b/libc/stdio/unlocked/putwc_unlocked.S @@ -18,11 +18,13 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -putwc_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call fputwc_unlocked - pop %rbp - ret - .endfn putwc_unlocked,globl +// Writes wide character to stream. +// +// @param rdi has wide character +// @param rsi has file object +// @return wc if written or -1 w/ errno +// @see putwc_unlocked() +putwc: mov %rsi,%r11 + ezlea fputwc_unlocked,ax + jmp stdio_unlock + .endfn putwc,globl diff --git a/libc/stdio/unlocked/putwchar_unlocked.S b/libc/stdio/unlocked/putwchar_unlocked.S index 0fd34dee1..a24d6cd02 100644 --- a/libc/stdio/unlocked/putwchar_unlocked.S +++ b/libc/stdio/unlocked/putwchar_unlocked.S @@ -1,4 +1,3 @@ - /*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ │vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ @@ -19,11 +18,14 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -putwchar_unlocked: - push %rbp - mov %rsp,%rbp - .profilable # note: no consensus for threads exists in abis - call putwchar - pop %rbp - ret - .endfn putwchar_unlocked,globl +// Writes wide character to stdout. +// +// @param rdi has wide character +// @return wc if written or -1 w/ errno +// @see fputwc_unlocked() +putwchar: + lea stdout(%rip),%rsi + mov %rsi,%r11 + ezlea fputwc_unlocked,ax + jmp stdio_unlock + .endfn putwchar,globl diff --git a/libc/stdio/unlocked/stdio_unlock.S b/libc/stdio/unlocked/stdio_unlock.S new file mode 100644 index 000000000..49f11ce44 --- /dev/null +++ b/libc/stdio/unlocked/stdio_unlock.S @@ -0,0 +1,70 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 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/macros.internal.h" + +#define LOCK 0x2c /* see struct file in stdio.h */ + +// Wrapper for applying locking to stdio functions. +// +// This function is intended to be called by thunks. +// +// @param rax has the delegate function pointer +// @param rdi is passed along as an arg +// @param rsi is passed along as an arg +// @param rdx is passed along as an arg +// @param rcx is passed along as an arg +// @param r8 is passed along as an arg +// @param r9 is passed along as an arg +// @param r10 is passed along as an arg +// @param r11 has the FILE* obj pointer +// @return rax is passed along as result +// @return rdx is passed along as result +stdio_unlock: + push %rbp + mov %rsp,%rbp + +// acquires mutex + push %rcx + push %rdx + mov $1,%cl +0: mov LOCK(%r11),%dl # optimistic + test %dl,%dl + je 2f +1: pause # hyperyield + jmp 0b +2: mov %ecx,%edx + xchg LOCK(%r11),%dl # locks bus! + test %dl,%dl + jne 1b + pop %rdx + pop %rcx + +// calls delegate + push %rsi + push %r11 + call *%rax + pop %r11 + pop %rsi + +// releases mutex + movb $0,LOCK(%r11) + + pop %rbp + ret + .endfn stdio_unlock,globl diff --git a/libc/stdio/unlocked/ungetc_unlocked.S b/libc/stdio/unlocked/ungetc_unlocked.S new file mode 100644 index 000000000..967cadd3b --- /dev/null +++ b/libc/stdio/unlocked/ungetc_unlocked.S @@ -0,0 +1,30 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 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/macros.internal.h" + +// Pushes byte back to stream. +// +// @param rdi has character to push +// @param rds has stream object pointer +// @return rax has rdi on success or -1 w/ errno +// @see ungetc_unlocked() +ungetc: mov %rsi,%r11 + ezlea ungetc_unlocked,ax + jmp stdio_unlock + .endfn ungetc,globl diff --git a/libc/stdio/unlocked/ungetwc_unlocked.S b/libc/stdio/unlocked/ungetwc_unlocked.S new file mode 100644 index 000000000..0c17fce2c --- /dev/null +++ b/libc/stdio/unlocked/ungetwc_unlocked.S @@ -0,0 +1,31 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 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/macros.internal.h" + +// Pushes byte back to stream. +// +// @param rdi has character to push +// @param rds has stream object pointer +// @return rax has rdi on success or -1 w/ errno +// @see ungetwc_unlocked() +ungetwc: + mov %rsi,%r11 + ezlea ungetwc_unlocked,ax + jmp stdio_unlock + .endfn ungetwc,globl diff --git a/libc/stdio/vfprintf.c b/libc/stdio/vfprintf.c index 96b354013..1fb65e33c 100644 --- a/libc/stdio/vfprintf.c +++ b/libc/stdio/vfprintf.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/fmt/fmt.h" +#include "libc/intrin/spinlock.h" #include "libc/limits.h" #include "libc/stdio/stdio.h" #include "libc/sysv/errfuns.h" @@ -28,14 +29,23 @@ struct state { }; static int vfprintfputchar(const char *s, struct state *t, size_t n) { + int rc; if (n) { + _spinlock(&t->f->lock); if (n == 1 && *s != '\n' && t->f->beg < t->f->size && t->f->bufmode != _IONBF) { t->f->buf[t->f->beg++] = *s; - } else if (!fwrite(s, 1, n, t->f)) { - return -1; + t->n += n; + rc = 0; + } else if (!fwrite_unlocked(s, 1, n, t->f)) { + rc = -1; + } else { + t->n += n; + rc = 0; } - t->n += n; + _spunlock(&t->f->lock); + } else { + rc = 0; } return 0; } diff --git a/libc/sysv/calls/mincore.s b/libc/sysv/calls/mincore.s deleted file mode 100644 index ad12de95b..000000000 --- a/libc/sysv/calls/mincore.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/macros.internal.inc" -.scall mincore,0x04e04e04e204e01b,globl diff --git a/libc/sysv/calls/sys_mincore.s b/libc/sysv/calls/sys_mincore.s new file mode 100644 index 000000000..e915c9b7c --- /dev/null +++ b/libc/sysv/calls/sys_mincore.s @@ -0,0 +1,2 @@ +.include "o/libc/sysv/macros.internal.inc" +.scall sys_mincore,0x04e04e04e204e01b,globl,hidden diff --git a/libc/sysv/syscalls.sh b/libc/sysv/syscalls.sh index e74f31739..4d1e40108 100755 --- a/libc/sysv/syscalls.sh +++ b/libc/sysv/syscalls.sh @@ -62,7 +62,7 @@ scall pselect 0x1b406e20a218afff globl scall pselect6 0xfffffffffffff10e globl scall sys_sched_yield 0x15e12a14b103c018 globl hidden # swtch() on xnu scall __sys_mremap 0x19bffffffffff019 globl hidden -scall mincore 0x04e04e04e204e01b globl +scall sys_mincore 0x04e04e04e204e01b globl hidden scall sys_madvise 0x04b04b04b204b01c globl hidden scall shmget 0x0e71210e7210901d globl # consider mmap scall shmat 0x0e40e40e4210601e globl # consider mmap diff --git a/libc/time/difftime.c b/libc/time/difftime.c index 00f1daa00..2f2d61897 100644 --- a/libc/time/difftime.c +++ b/libc/time/difftime.c @@ -1,6 +1,7 @@ /*-*- mode:c; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ │vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/macros.internal.h" #include "libc/time/tz.internal.h" // clang-format off /* Return the difference between two timestamps. */ diff --git a/libc/time/localtime.c b/libc/time/localtime.c index 4fcca25b2..50c68da0f 100644 --- a/libc/time/localtime.c +++ b/libc/time/localtime.c @@ -2,6 +2,7 @@ │vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│ ╚─────────────────────────────────────────────────────────────────────────────*/ #define LOCALTIME_IMPLEMENTATION +#include "libc/bits/bits.h" #include "libc/calls/calls.h" #include "libc/intrin/spinlock.h" #include "libc/str/str.h" @@ -241,50 +242,12 @@ ttunspecified(struct state const *sp, int i) return memcmp(abbr, UNSPEC, sizeof UNSPEC) == 0; } -static int_fast32_t -detzcode(const char *const codep) -{ - register int_fast32_t result; - register int i; - int_fast32_t one = 1; - int_fast32_t halfmaxval = one << (32 - 2); - int_fast32_t maxval = halfmaxval - 1 + halfmaxval; - int_fast32_t minval = -1 - maxval; - - result = codep[0] & 0x7f; - for (i = 1; i < 4; ++i) - result = (result << 8) | (codep[i] & 0xff); - - if (codep[0] & 0x80) { - /* Do two's-complement negation even on non-two's-complement machines. - If the result would be minval - 1, return minval. */ - result -= !TWOS_COMPLEMENT(int_fast32_t) && result != 0; - result += minval; - } - return result; +forceinline int_fast32_t detzcode(const char *const codep) { + return READ32BE(codep); } -static int_fast64_t -detzcode64(const char *const codep) -{ - register int_fast64_t result; - register int i; - int_fast64_t one = 1; - int_fast64_t halfmaxval = one << (64 - 2); - int_fast64_t maxval = halfmaxval - 1 + halfmaxval; - int_fast64_t minval = -TWOS_COMPLEMENT(int_fast64_t) - maxval; - - result = codep[0] & 0x7f; - for (i = 1; i < 8; ++i) - result = (result << 8) | (codep[i] & 0xff); - - if (codep[0] & 0x80) { - /* Do two's-complement negation even on non-two's-complement machines. - If the result would be minval - 1, return minval. */ - result -= !TWOS_COMPLEMENT(int_fast64_t) && result != 0; - result += minval; - } - return result; +forceinline int_fast64_t detzcode64(const char *const codep) { + return READ64BE(codep); } static void diff --git a/libc/time/tz.internal.h b/libc/time/tz.internal.h index 8dd16b6da..93095d983 100644 --- a/libc/time/tz.internal.h +++ b/libc/time/tz.internal.h @@ -5,6 +5,7 @@ #include "libc/errno.h" #include "libc/inttypes.h" #include "libc/limits.h" +#include "libc/macros.internal.h" #include "libc/runtime/runtime.h" #include "libc/sysv/consts/ok.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) @@ -376,8 +377,6 @@ int64_t time2posix_z(timezone_t, int64_t) nosideeffect; ** Finally, some convenience items. */ -#define TYPE_BIT(type) (sizeof(type) * CHAR_BIT) -#define TYPE_SIGNED(type) (((type) -1) < 0) #define TWOS_COMPLEMENT(t) ((t) ~ (t) 0 < 0) /* Max and min values of the integer type T, of which only the bottom diff --git a/libc/zipos/find.c b/libc/zipos/find.c index ac0847b14..df5ab4f15 100644 --- a/libc/zipos/find.c +++ b/libc/zipos/find.c @@ -23,6 +23,8 @@ #include "libc/zip.h" #include "libc/zipos/zipos.internal.h" +// TODO(jart): improve time complexity here + ssize_t __zipos_find(struct Zipos *zipos, const struct ZiposUri *name) { const char *zname; size_t i, n, c, znamesize; diff --git a/test/libc/rand/rand64_test.c b/test/libc/rand/rand64_test.c index ba76907cc..3da60dc10 100644 --- a/test/libc/rand/rand64_test.c +++ b/test/libc/rand/rand64_test.c @@ -91,9 +91,10 @@ TEST(rand64, testThreadSafety_doesntProduceIdenticalValues) { for (i = 0; i < THREADS; ++i) { stacks[i] = mmap(0, GetStackSize(), PROT_READ | PROT_WRITE, MAP_STACK | MAP_ANONYMOUS, -1, 0); - tid[i] = clone(Thrasher, stacks[i], GetStackSize(), - CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, - (void *)(intptr_t)i, 0, 0, 0, 0); + tid[i] = + clone(Thrasher, stacks[i], GetStackSize(), + CLONE_THREAD | CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, + (void *)(intptr_t)i, 0, 0, 0, 0); ASSERT_NE(-1, tid[i]); } ready = true; diff --git a/test/libc/runtime/clone_test.c b/test/libc/runtime/clone_test.c index 6cddceb71..6ce94214b 100644 --- a/test/libc/runtime/clone_test.c +++ b/test/libc/runtime/clone_test.c @@ -60,7 +60,8 @@ TEST(clone, test1) { int tid; _spinlock(&lock); ASSERT_NE(-1, (tid = clone(CloneTest1, stack, GetStackSize(), - CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, + CLONE_THREAD | CLONE_VM | CLONE_FS | CLONE_FILES | + CLONE_SIGHAND, (void *)23, 0, 0, 0, 0))); _spinlock(&lock); ASSERT_EQ(42, x); diff --git a/third_party/lua/liolib.c b/third_party/lua/liolib.c index 92027cb1b..2709b6e59 100644 --- a/third_party/lua/liolib.c +++ b/third_party/lua/liolib.c @@ -30,8 +30,8 @@ #include "libc/calls/calls.h" #include "libc/calls/weirdtypes.h" #include "libc/errno.h" +#include "libc/stdio/stdio.h" #include "libc/stdio/temp.h" -#include "libc/stdio/unlocked.h" #include "third_party/lua/lauxlib.h" #include "third_party/lua/lprefix.h" #include "third_party/lua/lua.h" diff --git a/third_party/lua/lmem.c b/third_party/lua/lmem.c index c83b25f86..8025eab7c 100644 --- a/third_party/lua/lmem.c +++ b/third_party/lua/lmem.c @@ -27,6 +27,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #define lmem_c #define LUA_CORE +#include "libc/log/log.h" #include "third_party/lua/ldebug.h" #include "third_party/lua/ldo.h" #include "third_party/lua/lgc.h" @@ -170,6 +171,7 @@ static void *tryagain (lua_State *L, void *block, size_t osize, size_t nsize) { global_State *g = G(L); if (completestate(g) && !g->gcstopem) { + WARNF("reacting to malloc() failure by running lua garbage collector..."); luaC_fullgc(L, 1); /* try to free some memory... */ return (*g->frealloc)(g->ud, block, osize, nsize); /* try again */ } diff --git a/third_party/lz4cli/lz4cli.c b/third_party/lz4cli/lz4cli.c index 1ce02243e..2903cc9c8 100644 --- a/third_party/lz4cli/lz4cli.c +++ b/third_party/lz4cli/lz4cli.c @@ -46,6 +46,7 @@ asm(".include \"third_party/lz4cli/COPYING\""); #include "third_party/lz4cli/lz4hc.h" /* LZ4HC_CLEVEL_MAX */ #include "libc/runtime/runtime.h" #include "libc/log/log.h" +#include "libc/stdio/stdio.h" #include "third_party/lz4cli/lz4.h" /* LZ4_VERSION_STRING */ diff --git a/third_party/python/Objects/fileobject.c b/third_party/python/Objects/fileobject.c index c02bbdce1..afe196670 100644 --- a/third_party/python/Objects/fileobject.c +++ b/third_party/python/Objects/fileobject.c @@ -7,7 +7,6 @@ #define PY_SSIZE_T_CLEAN #include "libc/calls/calls.h" #include "libc/errno.h" -#include "libc/stdio/unlocked.h" #include "third_party/python/Include/abstract.h" #include "third_party/python/Include/boolobject.h" #include "third_party/python/Include/bytesobject.h" diff --git a/third_party/python/pyconfig.h b/third_party/python/pyconfig.h index c3ff787f0..924801cc6 100644 --- a/third_party/python/pyconfig.h +++ b/third_party/python/pyconfig.h @@ -150,9 +150,9 @@ /* #define HAVE_INITGROUPS 1 */ /* #define HAVE_GETGROUPLIST 1 */ -#define HAVE_FSEEKO 1 -#define HAVE_FTELLO 1 -/* #undef HAVE_GETC_UNLOCKED */ +#define HAVE_FSEEKO 1 +#define HAVE_FTELLO 1 +#define HAVE_GETC_UNLOCKED 1 #define HAVE_GETADDRINFO 1 #define HAVE_GAI_STRERROR 1 diff --git a/third_party/sqlite3/os_unix.c b/third_party/sqlite3/os_unix.c index 62907d5ae..e84cb8e1d 100644 --- a/third_party/sqlite3/os_unix.c +++ b/third_party/sqlite3/os_unix.c @@ -317,6 +317,7 @@ static pid_t randomnessPid = 0; #define threadid 0 #endif +#if 0 /* ** HAVE_MREMAP defaults to true on Linux and false everywhere else. */ @@ -327,6 +328,7 @@ static pid_t randomnessPid = 0; # define HAVE_MREMAP 0 # endif #endif +#endif #ifdef __linux__ /* diff --git a/third_party/sqlite3/sqlite3.mk b/third_party/sqlite3/sqlite3.mk index 90a1a02b7..9880d2a90 100644 --- a/third_party/sqlite3/sqlite3.mk +++ b/third_party/sqlite3/sqlite3.mk @@ -108,6 +108,7 @@ THIRD_PARTY_SQLITE3_FLAGS = \ -DBUILD_sqlite \ -DHAVE_USLEEP \ -DHAVE_READLINK \ + -DHAVE_FCHOWN \ -DHAVE_LSTAT \ -DHAVE_GMTIME_R \ -DHAVE_FDATASYNC \ diff --git a/tool/net/demo/.lua/mymodule.lua b/tool/net/demo/.lua/mymodule.lua index 54f4e395d..3ffea3c73 100644 --- a/tool/net/demo/.lua/mymodule.lua +++ b/tool/net/demo/.lua/mymodule.lua @@ -1,9 +1,6 @@ local mymodule = {} function mymodule.hello() - SetStatus(200) - SetHeader('Content-Type', 'text/html; charset=US-ASCII') - Write("\r\n") Write("Hello World!\r\n") end diff --git a/tool/net/demo/binarytrees.lua b/tool/net/demo/binarytrees.lua new file mode 100644 index 000000000..10cbe8532 --- /dev/null +++ b/tool/net/demo/binarytrees.lua @@ -0,0 +1,189 @@ +-- binary trees: benchmark game +-- with resource limit tutorial +-- by justine tunney + +local outofcpu = false +local oldsigxcpufunc = nil +local oldsigxcpuflags = nil +local oldsigxcpumask = nil + +-- This is our signal handler. +function OnSigxcpu(sig) + -- Please note, it's dangerous to do complicated things from inside + -- asynchronous signal handlers. Even Log() isn't async signal safe + -- The best possible practice is to have them simply set a variable + outofcpu = true +end + +-- These two functions are what's being benchmarked. It's a popular +-- torture test for interpreted languages since it generates a lot of +-- garbage. Lua does well on this test, going faster than many other +-- languages, e.g. Racket, Python. + +local function MakeTree(depth) + if outofcpu then + error({reason='MakeTree() caught SIGXCPU'}) + end + if depth > 0 then + depth = depth - 1 + left, right = MakeTree(depth), MakeTree(depth) + return { left, right } + else + return { } + end +end + +local function CheckTree(tree) + if outofcpu then + error({reason='CheckTree() caught SIGXCPU'}) + end + if tree[1] then + return 1 + CheckTree(tree[1]) + CheckTree(tree[2]) + else + return 1 + end +end + +-- Now for our redbean web server code... + +local function WriteForm(depth, suggestion) + Write([[ + + redbean binary trees + + + +

+ + binary trees benchmark demo +

+ +

+ This demo is from the computer language benchmark game. It's a + torture test for the Lua garbage collector. ProTip: Try loading + this page while the terminal memory monitor feature is active, so + you can see memory shuffle around in a dual screen display. We + use setrlimit to set a 512mb memory limit. So if you specify a + number higher than 23 (a good meaty benchmark) then the Lua GC + should panic a bit trying to recover (due to malloc failure) and + ultimately the worker process should die. However the server and + your system will survive. +

+ +
+ + +
+ + ]] % {EscapeHtml(depth), suggestion}) +end + +local function BinaryTreesDemo() + if GetMethod() == 'GET' or GetMethod() == 'HEAD' then + WriteForm("18", 18) + elseif GetMethod() == 'POST' and not HasParam('depth') then + ServeError(400) + elseif GetMethod() == 'POST' then + + -- write out the page so user can submit again + WriteForm(GetParam('depth'), 18) + Write('
\r\n') + Write('
Output\r\n') + Write('
\r\n') + + -- impose quotas to protect the server + unix.setrlimit(unix.RLIMIT_AS, 1024 * 1024 * 1024) + unix.setrlimit(unix.RLIMIT_CPU, 2, 4) + + -- when RLIMIT_AS (virtual address space) runs out of memory, then + -- mmap() and malloc() start to fail, and lua reacts by trying to + -- run the garbage collector over and over again. so we also place + -- a limit on the number of "cpu time" seconds too. the behavior + -- when we hit the soft limit, is the kernel gives us a friendly + -- warning by sending the signal SIGXCPU which we must catch here + -- once we catch it, we've got 2 seconds of grace time to clean up + -- before we hit the "hard limit" and the kernel sends SIGKILL (9) + oldsigxcpufunc, + oldsigxcpuflags, + oldsigxcpumask = + unix.sigaction(unix.SIGXCPU, OnSigxcpu) + + -- get the user-supplied parameter + depth = tonumber(GetParam('depth')) + + -- run the benchmark, recording how long it takes + secs1, nanos1 = unix.clock_gettime() + res = CheckTree(MakeTree(depth)) + secs2, nanos2 = unix.clock_gettime() + + -- turn 128-bit timestamps into 64-bit nanosecond interval + if secs2 == secs1 then + nanos = nanos2 - nanos1 + else + nanos = (secs2 - secs1) * 1000000000 + (1000000000 - nanos1) + nanos2 + end + + -- write out result of benchmark + Write('0%o\r\n' % {res}) + Write('
Time Elapsed\r\n') + Write('
\r\n') + Write('%g seconds, or
\r\n' % {nanos / 1e9}) + Write('%g milliseconds, or
\r\n' % {nanos / 1e6}) + Write('%g microseconds, or
\r\n' % {nanos / 1e6}) + Write('%d nanoseconds\r\n' % {nanos}) + Write('
unix.clock_gettime() #1\r\n') + Write('
%d, %d\r\n' % {secs1, nanos1}) + Write('
unix.clock_gettime() #2\r\n') + Write('
%d, %d\r\n' % {secs2, nanos2}) + Write('
\r\n') + + else + ServeError(405) + SetHeader('Allow', 'GET, HEAD, POST') + end +end + +local function main() + -- catch exceptions + ok, err = pcall(BinaryTreesDemo) + + -- we don't need to restore the old handler + -- but it's generally a good practice to clean up + if oldsigxcpufunc then + unix.sigaction(unix.SIGXCPU, + oldsigxcpufunc, + oldsigxcpuflags, + oldsigxcpumask) + end + + -- handle exceptions + if not ok then + + -- whenever anything, at all, goes wrong, with anything + -- always with few exceptions close the connection asap + SetHeader('Connection', 'close') + + if err.reason then + -- show our error message withoun internal code leaking out + Write(err.reason) + Write('\r\n') + else + -- just rethrow exception + error('unexpected failure: ' .. tostring(err)) + end + + end +end + +main() diff --git a/tool/net/demo/call-lua-module.lua b/tool/net/demo/call-lua-module.lua new file mode 100644 index 000000000..a89c432fd --- /dev/null +++ b/tool/net/demo/call-lua-module.lua @@ -0,0 +1,49 @@ +-- Call Lua Module Demo +-- +-- Your Lua modules may be stored in the /.lua/ folder. +-- +-- In our /.init.lua global scope earlier, we ran the code: +-- +-- mymodule = require "mymodule" +-- +-- Which preloaded the /.lua/mymodule.lua module once into the server +-- global memory template from which all request handlers are forked. +-- Therefore, we can just immediately use that module from our Lua +-- server pages. + +Write[[ + redbean call lua module demo + +

+ + call lua module demo +

+

Your Lua modules may be stored in the /.lua/ folder. +

In our /.init.lua global scope earlier, we ran the code: +

mymodule = require "mymodule"
+

Which preloaded the /.lua/mymodule.lua module once into + the server global memory template from which all request handlers are + forked. Therefore, we can just immediately use that module from our + Lua Server Pages. +

+ Your mymodule.hello() output is as follows: +

+]] + +mymodule.hello() + +Write[[ +
+

+ go back +

+]] diff --git a/tool/net/demo/fetch.lua b/tool/net/demo/fetch.lua index ff28742aa..db64c484b 100644 --- a/tool/net/demo/fetch.lua +++ b/tool/net/demo/fetch.lua @@ -1,41 +1,29 @@ -- Fetch() API Demo local function WriteForm(url) - Write('\r\n') - Write([[ + Write([[ redbean fetch demo

redbean fetch demo

+

+ Your redbean is able to function as an HTTP client too. + Lua server pages can use the Fetch() API to + to send outgoing HTTP and HTTPS requests to other web + servers. All it takes is a line of code! +

diff --git a/tool/net/demo/hello.lua b/tool/net/demo/hello.lua deleted file mode 100644 index 38226b112..000000000 --- a/tool/net/demo/hello.lua +++ /dev/null @@ -1,4 +0,0 @@ -if not IsPublicIp(GetClientAddr()) then - StoreAsset('/hi', 'sup') -end -mymodule.hello() diff --git a/tool/net/demo/maxmind.lua b/tool/net/demo/maxmind.lua index 898315737..fb0cd5ceb 100644 --- a/tool/net/demo/maxmind.lua +++ b/tool/net/demo/maxmind.lua @@ -1,3 +1,6 @@ +-- redbean maxmind demo +-- by justine tunney + local maxmind = require "maxmind" local kMetroCodes = { @@ -258,7 +261,7 @@ local function main() local ip = nil local geo = nil local asn = nil - local value = '' + local value = '8.8.8.8' if HasParam('ip') then local geodb = maxmind.open('/usr/local/share/maxmind/GeoLite2-City.mmdb') local asndb = maxmind.open('/usr/local/share/maxmind/GeoLite2-ASN.mmdb') @@ -279,18 +282,58 @@ local function main() end end - SetHeader('Content-Type', 'text/html; charset=utf-8') - Write('\n') - Write([[ - maxmind redbean demo + Write([[ + redbean maxmind demo + +

+ + redbean maxmind demo +

+

+ Your redbean supports MaxMind GeoLite2 which is a free database + you have to download separately, at their website + here. It's worth doing because it lets you turn IP addresses + into geographical coordinates, addresses, name it. Being able to + Lua script this database is going to help you address things like + online fraud and abuse. +

+

+ This script is hard coded to assume the database is at the + following paths: +

+

+ Which on Windows basically means the same thing as: +

+ +

+ Once you've placed it there, you can fill out the form below to + have fun crawling all the information it provides! +

+ value="%s" onfocus="this.select()" autofocus>
- +
- ]]) + ]] % {EscapeHtml(value)}) if ip then Write('

Maxmind Geolite DB

') diff --git a/tool/net/demo/store-asset.lua b/tool/net/demo/store-asset.lua new file mode 100644 index 000000000..320eb3f19 --- /dev/null +++ b/tool/net/demo/store-asset.lua @@ -0,0 +1,80 @@ +-- StoreAsset Demo +-- +-- Redbean is able to store files into its own executable structure. +-- This is currently only supported on a Linux, Apple, and FreeBSD. +-- +-- By loading this page, your redbean will insert an immediate file into +-- your redbean named `/hi`, which you can click the back button in the +-- browser and view it on the listing page right afterwards! + +Write[[ + redbean store asset demo + +

+ + store asset demo +

+]] + +if IsPublicIp(GetClientAddr()) then + Write[[ +

+ Bad request. +

+

+ This HTTP endpoint self-modifies the web server. You're + communicating wtith this redbean over a public network. + Therefore redbean won't service this request. +

+ ]] + +elseif GetHostOs() == "WINDOWS" or + GetHostOs() == "OPENBSD" or + GetHostOs() == "NETBSD" then + Write[[ +

+ Unsupported +

+

+ Sorry! Redbean's Lua StoreAsset() function is only + supported on Linux, Apple, and FreeBSD right now. +

+ ]] + +else + StoreAsset('/hi', [[ +StoreAsset() worked! + +This file was inserted into your redbean by the Lua StoreAsset() API +which was invoked by you browsing to the /store-asset.lua page. + +Enjoy your self-modifying web server! +]]) + + Write[[ +

+ This Lua script has just stored a new file named /hi + to your redbean zip executable. This was accomplished while the + web server is running. It live updates, so if you click the back + button in your browser, you should see /hi in the + ZIP central directory listing, and you can send an HTTP message + requesting it. +

+ ]] + +end + +Write[[ +

+ go back +

+]] diff --git a/tool/net/help.txt b/tool/net/help.txt index 76c083412..1c74ead80 100644 --- a/tool/net/help.txt +++ b/tool/net/help.txt @@ -70,6 +70,7 @@ FLAGS -p PORT listen port [def. 8080; repeatable] -l ADDR listen addr [def. 0.0.0.0; repeatable] -c SEC configures static cache-control + -W TTY use tty path to monitor memory pages -L PATH log file location -P PATH pid file location -U INT daemon set user id diff --git a/tool/net/lunix.c b/tool/net/lunix.c index 5e615954e..11ef20ff8 100644 --- a/tool/net/lunix.c +++ b/tool/net/lunix.c @@ -109,6 +109,7 @@ static void *LuaUnixRealloc(lua_State *L, void *p, size_t n) { return p2; } if (IsLegalSize(n)) { + WARNF("reacting to malloc() failure by running lua garbage collector..."); luaC_fullgc(L, 1); p2 = realloc(p, n); } diff --git a/tool/net/net.mk b/tool/net/net.mk index 9b60d473c..6332a72b1 100644 --- a/tool/net/net.mk +++ b/tool/net/net.mk @@ -180,10 +180,12 @@ o/$(MODE)/tool/net/demo/unix-webserver.lua.zip.o \ o/$(MODE)/tool/net/demo/unix-dir.lua.zip.o \ o/$(MODE)/tool/net/demo/unix-info.lua.zip.o \ o/$(MODE)/tool/net/demo/fetch.lua.zip.o \ -o/$(MODE)/tool/net/demo/hello.lua.zip.o \ +o/$(MODE)/tool/net/demo/call-lua-module.lua.zip.o \ +o/$(MODE)/tool/net/demo/store-asset.lua.zip.o \ o/$(MODE)/tool/net/demo/maxmind.lua.zip.o \ o/$(MODE)/tool/net/demo/redbean.lua.zip.o \ o/$(MODE)/tool/net/demo/opensource.lua.zip.o \ +o/$(MODE)/tool/net/demo/binarytrees.lua.zip.o \ o/$(MODE)/tool/net/demo/crashreport.lua.zip.o \ o/$(MODE)/tool/net/demo/closedsource.lua.zip.o \ o/$(MODE)/tool/net/demo/printpayload.lua.zip.o \ @@ -227,10 +229,12 @@ o/$(MODE)/tool/net/redbean-demo.com.dbg: \ o/$(MODE)/tool/net/demo/unix-dir.lua.zip.o \ o/$(MODE)/tool/net/demo/unix-info.lua.zip.o \ o/$(MODE)/tool/net/demo/fetch.lua.zip.o \ - o/$(MODE)/tool/net/demo/hello.lua.zip.o \ + o/$(MODE)/tool/net/demo/store-asset.lua.zip.o \ + o/$(MODE)/tool/net/demo/call-lua-module.lua.zip.o \ o/$(MODE)/tool/net/demo/redbean.lua.zip.o \ o/$(MODE)/tool/net/demo/maxmind.lua.zip.o \ o/$(MODE)/tool/net/demo/opensource.lua.zip.o \ + o/$(MODE)/tool/net/demo/binarytrees.lua.zip.o \ o/$(MODE)/tool/net/demo/crashreport.lua.zip.o \ o/$(MODE)/tool/net/demo/closedsource.lua.zip.o \ o/$(MODE)/tool/net/demo/printpayload.lua.zip.o \ diff --git a/tool/net/redbean.c b/tool/net/redbean.c index ed7d895c7..4063fef3a 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -22,6 +22,7 @@ #include "libc/bits/popcnt.h" #include "libc/bits/safemacros.internal.h" #include "libc/calls/calls.h" +#include "libc/calls/ioctl.h" #include "libc/calls/math.h" #include "libc/calls/sigbits.h" #include "libc/calls/strace.internal.h" @@ -31,12 +32,15 @@ #include "libc/calls/struct/rusage.h" #include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/stat.h" +#include "libc/calls/struct/termios.h" +#include "libc/calls/ttydefaults.h" #include "libc/dce.h" #include "libc/dns/dns.h" #include "libc/dns/hoststxt.h" #include "libc/dos.h" #include "libc/errno.h" #include "libc/fmt/conv.h" +#include "libc/fmt/fmt.h" #include "libc/fmt/itoa.h" #include "libc/intrin/nomultics.internal.h" #include "libc/intrin/spinlock.h" @@ -64,6 +68,7 @@ #include "libc/runtime/gc.h" #include "libc/runtime/gc.internal.h" #include "libc/runtime/internal.h" +#include "libc/runtime/memtrack.internal.h" #include "libc/runtime/runtime.h" #include "libc/runtime/stack.h" #include "libc/runtime/symbols.internal.h" @@ -78,6 +83,7 @@ #include "libc/sysv/consts/af.h" #include "libc/sysv/consts/audit.h" #include "libc/sysv/consts/auxv.h" +#include "libc/sysv/consts/clone.h" #include "libc/sysv/consts/dt.h" #include "libc/sysv/consts/ex.h" #include "libc/sysv/consts/exit.h" @@ -102,6 +108,7 @@ #include "libc/sysv/consts/sock.h" #include "libc/sysv/consts/sol.h" #include "libc/sysv/consts/tcp.h" +#include "libc/sysv/consts/termios.h" #include "libc/sysv/consts/w.h" #include "libc/sysv/errfuns.h" #include "libc/testlib/testlib.h" @@ -183,9 +190,11 @@ STATIC_YOINK("zip_uri_support"); #define VERSION 0x020000 #define HEARTBEAT 5000 /*ms*/ #define HASH_LOAD_FACTOR /* 1. / */ 4 +#define MONITOR_MICROS 150000 #define READ(F, P, N) readv(F, &(struct iovec){P, N}, 1) #define WRITE(F, P, N) writev(F, &(struct iovec){P, N}, 1) #define LockInc(P) asm volatile("lock incq\t%0" : "=m"(*(P))) +#define LockDec(P) asm volatile("lock decq\t%0" : "=m"(*(P))) #define AppendCrlf(P) mempcpy(P, "\r\n", 2) #define HasHeader(H) (!!msg.headers[H].a) #define HeaderData(H) (inbuf.p + msg.headers[H].a) @@ -193,26 +202,34 @@ STATIC_YOINK("zip_uri_support"); #define HeaderEqualCase(H, S) \ SlicesEqualCase(S, strlen(S), HeaderData(H), HeaderLength(H)) -#define TRACE_BEGIN \ - do { \ - if (!IsTiny()) { \ - if (funtrace) ++g_ftrace; \ - if (systrace) ++__strace; \ - } \ +#define TRACE_BEGIN \ + do { \ + if (!IsTiny()) { \ + if (funtrace) { \ + __atomic_fetch_add(&g_ftrace, 1, __ATOMIC_RELAXED); \ + } \ + if (systrace) { \ + __atomic_fetch_add(&__strace, 1, __ATOMIC_RELAXED); \ + } \ + } \ } while (0) -#define TRACE_END \ - do { \ - if (!IsTiny()) { \ - if (funtrace) --g_ftrace; \ - if (systrace) --__strace; \ - } \ +#define TRACE_END \ + do { \ + if (!IsTiny()) { \ + if (funtrace) { \ + __atomic_fetch_sub(&g_ftrace, 1, __ATOMIC_RELAXED); \ + } \ + if (systrace) { \ + __atomic_fetch_sub(&__strace, 1, __ATOMIC_RELAXED); \ + } \ + } \ } while (0) -// letters not used: EIJNOQWXYnoqwxy +// letters not used: EIJNOQXYnoqwxy // digits not used: 0123456789 // puncts not used: !"#$%&'()*+,-./;<=>@[\]^_`{|}~ -#define GETOPTS "BSVZabdfghijkmsuvzA:C:D:F:G:H:K:L:M:P:R:T:U:c:e:l:p:r:t:" +#define GETOPTS "BSVZabdfghijkmsuvzA:C:D:F:G:H:K:L:M:P:R:T:U:W:c:e:l:p:r:t:" extern unsigned long long __kbirth; @@ -362,6 +379,7 @@ static struct Shared { #include "tool/net/counters.inc" #undef C } c; + _Alignas(64) char montermlock; } * shared; static const char kCounterNames[] = @@ -392,6 +410,7 @@ static bool sslcliused; static bool loglatency; static bool terminated; static bool uniprocess; +static bool memmonalive; static bool invalidated; static bool logmessages; static bool isinitialized; @@ -404,6 +423,7 @@ static bool sslclientverify; static bool connectionclose; static bool hasonworkerstop; static bool isexitingworker; +static bool terminatemonitor; static bool hasonworkerstart; static bool leakcrashreports; static bool hasonhttprequest; @@ -420,6 +440,7 @@ static int zfd; static int frags; static int gmtoff; static int client; +static int mainpid; static int sandboxed; static int changeuid; static int changegid; @@ -459,6 +480,7 @@ static struct Strings loops; static size_t payloadlength; static size_t contentlength; static int64_t cacheseconds; +static const char *monitortty; static const char *serverheader; static struct Strings stagedirs; static struct Strings hidepaths; @@ -1188,19 +1210,20 @@ static void CallSimpleHookIfDefined(const char *s) { } static void ReportWorkerExit(int pid, int ws) { - --shared->workers; + int workers; + workers = __atomic_sub_fetch(&shared->workers, 1, __ATOMIC_SEQ_CST); if (WIFEXITED(ws)) { if (WEXITSTATUS(ws)) { LockInc(&shared->c.failedchildren); WARNF("(stat) %d exited with %d (%,d workers remain)", pid, - WEXITSTATUS(ws), shared->workers); + WEXITSTATUS(ws), workers); } else { - DEBUGF("(stat) %d exited (%,d workers remain)", pid, shared->workers); + DEBUGF("(stat) %d exited (%,d workers remain)", pid, workers); } } else { LockInc(&shared->c.terminatedchildren); WARNF("(stat) %d terminated with %s (%,d workers remain)", pid, - strsignal(WTERMSIG(ws)), shared->workers); + strsignal(WTERMSIG(ws)), workers); } } @@ -4369,7 +4392,7 @@ static int LuaSetHeader(lua_State *L) { } switch (h) { case kHttpConnection: - if (SlicesEqualCase(eval, evallen, "close", 5)) { + if (!SlicesEqualCase(eval, evallen, "close", 5)) { luaL_argerror(L, 2, "unsupported"); unreachable; } @@ -6306,6 +6329,10 @@ static int ExitWorker(void) { isexitingworker = true; return eintr(); } + if (monitortty) { + terminatemonitor = true; + _spinlock(&memmonalive); + } _Exit(0); } @@ -6405,6 +6432,161 @@ static int EnableSandbox(void) { } } +static int MemoryMonitor(void *arg) { + static struct termios oldterm; + static int tty; + sigset_t ss; + bool done, ok; + size_t intervals; + struct winsize ws; + unsigned char rez; + struct termios term; + char *b, *addr, title[128]; + struct MemoryInterval *mi, *mi2; + long i, j, k, n, x, y, pi, gen, pages; + int rc, id, color, color2, workers; + _spinlock(&memmonalive); + __atomic_load(&shared->workers, &id, __ATOMIC_SEQ_CST); + DEBUGF("(memv) started for pid %d on tid %d", getpid(), gettid()); + + sigemptyset(&ss); + sigaddset(&ss, SIGHUP); + sigaddset(&ss, SIGINT); + sigaddset(&ss, SIGQUIT); + sigaddset(&ss, SIGTERM); + sigaddset(&ss, SIGPIPE); + sigaddset(&ss, SIGUSR1); + sigaddset(&ss, SIGUSR2); + sigprocmask(SIG_BLOCK, &ss, 0); + + _spinlock(&shared->montermlock); + if (!id) { + if ((tty = open(monitortty, O_RDWR | O_NOCTTY)) != -1) { + ioctl(tty, TCGETS, &oldterm); + term = oldterm; + term.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + term.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + term.c_oflag |= OPOST | ONLCR; + term.c_iflag |= IUTF8; + term.c_cflag |= CS8; + term.c_cc[VMIN] = 1; + term.c_cc[VTIME] = 0; + ioctl(tty, TCSETS, &term); + WRITE(tty, "\e[?25l", 6); + } + } + _spunlock(&shared->montermlock); + + if (tty != -1) { + for (gen = 0, mi = 0, b = 0;;) { + __atomic_load(&terminatemonitor, &done, __ATOMIC_SEQ_CST); + if (done) break; + __atomic_load(&shared->workers, &workers, __ATOMIC_SEQ_CST); + if (id) id = MAX(1, MIN(id, workers)); + if (!id && workers) { + usleep(50000); + continue; + } + + ++gen; + __atomic_load(&_mmi.i, &intervals, __ATOMIC_SEQ_CST); + if ((mi2 = realloc(mi, (intervals += 3) * sizeof(*mi)))) { + mi = mi2; + mi[0].x = (intptr_t)_base >> 16; + mi[0].size = _etext - _base; + mi[1].x = (intptr_t)_etext >> 16; + mi[1].size = _edata - _etext; + mi[2].x = (intptr_t)_edata >> 16; + mi[2].size = _end - _edata; + _spinlock(&_mmi.lock); + if (_mmi.i == intervals - 3) { + memcpy(mi + 3, _mmi.p, _mmi.i * sizeof(*mi)); + ok = true; + } else { + ok = false; + } + _spunlock(&_mmi.lock); + if (!ok) { + WARNF("(memv) retrying due to contention on mmap table"); + continue; + } + + ws.ws_col = 80; + ws.ws_row = 40; + getttysize(tty, &ws); + + appendr(&b, 0); + appends(&b, "\e[H\e[1m"); + + for (pi = k = x = y = i = 0; i < intervals; ++i) { + addr = (char *)((int64_t)((uint64_t)mi[i].x << 32) >> 16); + color = 0; + appendf(&b, "\e[0m%lx", addr); + pages = (mi[i].size + PAGESIZE - 1) / PAGESIZE; + for (j = 0; j < pages; ++j) { + rc = mincore(addr + j * PAGESIZE, PAGESIZE, &rez); + if (!rc) { + if (rez & 1) { + color2 = 42; + } else { + color2 = 41; + } + } else { + errno = 0; + color2 = 0; + } + if (color != color2) { + color = color2; + appendf(&b, "\e[%dm", color); + } + appendw(&b, ' '); + } + } + + appendf(&b, + "\e[0m ID=%d PID=%d WS=%dx%d WORKERS=%d MODE=" MODE + " GEN=%ld\e[J", + id, getpid(), ws.ws_col, ws.ws_row, workers, gen); + + _spinlock(&shared->montermlock); + WRITE(tty, b, appendz(b).i); + appendr(&b, 0); + usleep(MONITOR_MICROS); + _spunlock(&shared->montermlock); + } else { + // running out of memory temporarily is a real possibility here + // the right thing to do, is stand aside and let lua try to fix + WARNF("(memv) we require more vespene gas"); + usleep(MONITOR_MICROS); + } + } + + if (!id) { + appendr(&b, 0); + appends(&b, "\e[H\e[J\e[?25h"); + WRITE(tty, b, appendz(b).i); + ioctl(tty, TCSETS, &oldterm); + } + + DEBUGF("(memv) exiting..."); + close(tty); + free(mi); + free(b); + } + _spunlock(&memmonalive); + DEBUGF("(memv) done"); + return 0; +} + +static int MonitorMemory(void) { + return clone(MemoryMonitor, + mmap(0, FRAMESIZE, PROT_READ | PROT_WRITE, + MAP_STACK | MAP_ANONYMOUS, -1, 0), + FRAMESIZE, + CLONE_VM | CLONE_THREAD | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, + 0, 0, 0, 0, 0); +} + static int HandleConnection(size_t i) { int pid, rc = 0; clientaddrsize = sizeof(clientaddr); @@ -6423,6 +6605,10 @@ static int HandleConnection(size_t i) { } else { switch ((pid = fork())) { case 0: + if (monitortty) { + memmonalive = false; + MonitorMemory(); + } meltdown = false; __isworker = true; connectionclose = false; @@ -6441,7 +6627,7 @@ static int HandleConnection(size_t i) { HandleForkFailure(); return 0; default: - ++shared->workers; + LockInc(&shared->workers); close(client); ReseedRng(&rng, "parent"); if (hasonprocesscreate) { @@ -6551,11 +6737,11 @@ static int HandleReadline(void) { if (status < 0) { if (status == -1) { OnTerm(SIGHUP); // eof - INFOF("got repl eof"); + VERBOSEF("(repl) eof"); return -1; } else if (errno == EINTR) { errno = 0; - INFOF("got repl interrupt"); + VERBOSEF("(repl) interrupt"); return -1; } else if (errno == EAGAIN) { errno = 0; @@ -6715,7 +6901,7 @@ static void HandleShutdown(void) { // this function coroutines with linenoise static int EventLoop(int ms) { long double t; - DEBUGF("EventLoop()"); + DEBUGF("(repl) event loop"); while (!terminated) { errno = 0; if (zombied) { @@ -6761,7 +6947,7 @@ static void ReplEventLoop(void) { static uint32_t WindowsReplThread(void *arg) { int sig; lua_State *L = GL; - DEBUGF("WindowsReplThread()"); + DEBUGF("(repl) started windows thread"); lua_repl_blocking = true; lua_repl_completions_callback = HandleCompletions; lua_initrepl(L, "redbean"); @@ -6781,7 +6967,7 @@ static uint32_t WindowsReplThread(void *arg) { if ((sig = linenoiseGetInterrupt())) { raise(sig); } - DEBUGF("WindowsReplThread() exiting"); + DEBUGF("(repl) terminating windows thread"); return 0; } @@ -6798,7 +6984,6 @@ static void SigInit(void) { InstallSignalHandler(SIGUSR1, OnUsr1); InstallSignalHandler(SIGUSR2, OnUsr2); InstallSignalHandler(SIGPIPE, SIG_IGN); - /* TODO(jart): SIGXCPU and SIGXFSZ */ } static void TlsInit(void) { @@ -6915,6 +7100,7 @@ static void GetOpts(int argc, char *argv[]) { CASE('u', uniprocess = true); CASE('g', loglatency = true); CASE('m', logmessages = true); + CASE('W', monitortty = optarg); CASE('l', ProgramAddr(optarg)); CASE('H', ProgramHeader(optarg)); CASE('L', ProgramLogPath(optarg)); @@ -6968,6 +7154,7 @@ void RedBean(int argc, char *argv[]) { reader = read; writer = WritevAll; gmtoff = GetGmtOffset((lastrefresh = startserver = nowl())); + mainpid = getpid(); CHECK_GT(CLK_TCK, 0); CHECK_NE(MAP_FAILED, (shared = mmap(NULL, ROUNDUP(sizeof(struct Shared), FRAMESIZE), @@ -6983,7 +7170,9 @@ void RedBean(int argc, char *argv[]) { GetOpts(argc, argv); LuaInit(); oldloglevel = __log_level; - if (uniprocess) shared->workers = 1; + if (uniprocess) { + shared->workers = 1; + } SigInit(); Listen(); TlsInit(); @@ -7008,6 +7197,12 @@ void RedBean(int argc, char *argv[]) { inbuf = inbuf_actual; isinitialized = true; CallSimpleHookIfDefined("OnServerStart"); + if (monitortty && (daemonize || uniprocess)) { + monitortty = 0; + } + if (monitortty) { + MonitorMemory(); + } #ifdef STATIC EventLoop(HEARTBEAT); #else @@ -7032,6 +7227,10 @@ void RedBean(int argc, char *argv[]) { TlsDestroy(); MemDestroy(); } + if (monitortty) { + terminatemonitor = true; + _spinlock(&memmonalive); + } INFOF("(srvr) shutdown complete"); }