Use re-entrant locks on stdio

This commit is contained in:
Justine Tunney 2022-05-22 08:13:13 -07:00
parent 4e9662cbc7
commit 1f229e4efc
78 changed files with 427 additions and 179 deletions

View file

@ -28,15 +28,18 @@
_Alignas(64) static int rlock; _Alignas(64) static int rlock;
static privileged inline bool AcquireInterruptPollLock(void) { // return 0 on success, or tid of other owner
static privileged inline int AcquireInterruptPollLock(void) {
// any thread can poll for interrupts // any thread can poll for interrupts
// but it's wasteful to have every single thread doing it // but it's wasteful to have every single thread doing it
int me, owner, tries; int me, owner = 0;
if (!__threaded) return true; if (__threaded) {
me = gettid(); me = gettid();
owner = 0; if (!_lockcmpxchgp(&rlock, &owner, me) && owner == me) {
if (_lockcmpxchgp(&rlock, &owner, me)) return true; owner = 0;
return owner == me; }
}
return owner;
} }
static textwindows inline void ReleaseInterruptPollLock(void) { static textwindows inline void ReleaseInterruptPollLock(void) {
@ -47,7 +50,7 @@ static textwindows inline void ReleaseInterruptPollLock(void) {
textwindows bool _check_interrupts(bool restartable, struct Fd *fd) { textwindows bool _check_interrupts(bool restartable, struct Fd *fd) {
bool res; bool res;
if (__time_critical) return false; if (__time_critical) return false;
if (!AcquireInterruptPollLock()) return false; if (AcquireInterruptPollLock()) return false;
if (weaken(_check_sigalrm)) weaken(_check_sigalrm)(); if (weaken(_check_sigalrm)) weaken(_check_sigalrm)();
if (weaken(_check_sigchld)) weaken(_check_sigchld)(); if (weaken(_check_sigchld)) weaken(_check_sigchld)();
if (fd && weaken(_check_sigwinch)) weaken(_check_sigwinch)(fd); if (fd && weaken(_check_sigwinch)) weaken(_check_sigwinch)(fd);

View file

@ -27,6 +27,12 @@ __msabi extern typeof(GetCurrentThreadId) *const __imp_GetCurrentThreadId;
/** /**
* Returns current thread id. * Returns current thread id.
*
* On Linux, and Linux only, this is guaranteed to be equal to getpid()
* if this is the main thread. On NetBSD, gettid() for the main thread
* is always 1.
*
* @return thread id greater than zero or -1 w/ errno
* @asyncsignalsafe * @asyncsignalsafe
*/ */
privileged int gettid(void) { privileged int gettid(void) {

View file

@ -64,7 +64,7 @@
do { \ do { \
autotype(lock) __lock = (lock); \ autotype(lock) __lock = (lock); \
typeof(*__lock) __x; \ typeof(*__lock) __x; \
int __tries = 0; \ unsigned __tries = 0; \
for (;;) { \ for (;;) { \
__atomic_load(__lock, &__x, __ATOMIC_RELAXED); \ __atomic_load(__lock, &__x, __ATOMIC_RELAXED); \
if (!__x && !_trylock_inline(__lock)) { \ if (!__x && !_trylock_inline(__lock)) { \

View file

@ -7,6 +7,7 @@
#include "libc/calls/struct/winsize.h" #include "libc/calls/struct/winsize.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/nexgen32e/stackframe.h" #include "libc/nexgen32e/stackframe.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h" #include "libc/stdio/stdio.h"
/*───────────────────────────────────────────────────────────────────────────│─╗ /*───────────────────────────────────────────────────────────────────────────│─╗
@ -89,7 +90,8 @@ extern unsigned __log_level; /* log level for runtime check */
--__ftrace; \ --__ftrace; \
flogf(kLogError, __FILE__, __LINE__, NULL, FMT, ##__VA_ARGS__); \ flogf(kLogError, __FILE__, __LINE__, NULL, FMT, ##__VA_ARGS__); \
if (weaken(__die)) weaken(__die)(); \ if (weaken(__die)) weaken(__die)(); \
exit(1); \ __restorewintty(); \
_Exit(1); \
unreachable; \ unreachable; \
} while (0) } while (0)

View file

@ -41,24 +41,25 @@
#define kNontrivialSize (8 * 1000 * 1000) #define kNontrivialSize (8 * 1000 * 1000)
static struct timespec vflogf_ts; static struct timespec vflogf_ts;
_Alignas(64) static int vflogf_lock;
/** /**
* Takes corrective action if logging is on the fritz. * Takes corrective action if logging is on the fritz.
*/ */
void vflogf_onfail(FILE *f) { static void vflogf_onfail(FILE *f) {
errno_t err; errno_t err;
int64_t size; int64_t size;
if (IsTiny()) return; if (IsTiny()) return;
err = ferror(f); err = ferror_unlocked(f);
if (fileno(f) != -1 && (err == ENOSPC || err == EDQUOT || err == EFBIG) && if (fileno_unlocked(f) != -1 &&
((size = getfiledescriptorsize(fileno(f))) == -1 || (err == ENOSPC || err == EDQUOT || err == EFBIG) &&
((size = getfiledescriptorsize(fileno_unlocked(f))) == -1 ||
size > kNontrivialSize)) { size > kNontrivialSize)) {
ftruncate(fileno(f), 0); ftruncate(fileno_unlocked(f), 0);
fseek(f, SEEK_SET, 0); fseeko_unlocked(f, SEEK_SET, 0);
f->beg = f->end = 0; f->beg = f->end = 0;
clearerr(f); clearerr_unlocked(f);
(fprintf)(f, "performed emergency log truncation: %s\n", strerror(err)); (fprintf_unlocked)(f, "performed emergency log truncation: %s\n",
strerror(err));
} }
} }
@ -88,8 +89,8 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f,
int64_t secs, nsec, dots; int64_t secs, nsec, dots;
if (!f) f = __log_file; if (!f) f = __log_file;
if (!f) return; if (!f) return;
_spinlock(&vflogf_lock); flockfile(f);
__atomic_fetch_sub(&__strace, 1, __ATOMIC_RELAXED); --__strace;
t2 = nowl(); t2 = nowl();
secs = t2; secs = t2;
@ -105,16 +106,17 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f,
bufmode = f->bufmode; bufmode = f->bufmode;
if (bufmode == _IOLBF) f->bufmode = _IOFBF; if (bufmode == _IOLBF) f->bufmode = _IOFBF;
if ((fprintf)(f, "%r%c%s%06ld:%s:%d:%.*s:%d] ", "FEWIVDNT"[level & 7], buf32, if ((fprintf_unlocked)(f, "%r%c%s%06ld:%s:%d:%.*s:%d] ",
rem1000000int64(div1000int64(dots)), file, line, "FEWIVDNT"[level & 7], buf32,
strchrnul(prog, '.') - prog, prog, getpid()) <= 0) { rem1000000int64(div1000int64(dots)), file, line,
strchrnul(prog, '.') - prog, prog, getpid()) <= 0) {
vflogf_onfail(f); vflogf_onfail(f);
} }
(vfprintf)(f, fmt, va); (vfprintf_unlocked)(f, fmt, va);
fprintf(f, "\n"); fputc_unlocked('\n', f);
if (bufmode == _IOLBF) { if (bufmode == _IOLBF) {
f->bufmode = _IOLBF; f->bufmode = _IOLBF;
fflush(f); fflush_unlocked(f);
} }
if (level == kLogFatal) { if (level == kLogFatal) {
@ -122,11 +124,10 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f,
strcpy(buf32, "unknown"); strcpy(buf32, "unknown");
gethostname(buf32, sizeof(buf32)); gethostname(buf32, sizeof(buf32));
(dprintf)(STDERR_FILENO, "fatality %s pid %d\n", buf32, getpid()); (dprintf)(STDERR_FILENO, "fatality %s pid %d\n", buf32, getpid());
_spunlock(&vflogf_lock);
__die(); __die();
unreachable; unreachable;
} }
__atomic_fetch_add(&__strace, 1, __ATOMIC_RELAXED); ++__strace;
_spunlock(&vflogf_lock); funlockfile(f);
} }

View file

@ -27,6 +27,7 @@
* @return memory address, or NULL w/ errno * @return memory address, or NULL w/ errno
* @throw EINVAL if !IS2POW(a) * @throw EINVAL if !IS2POW(a)
* @see pvalloc() * @see pvalloc()
* @threadsafe
*/ */
void *aligned_alloc(size_t a, size_t n) { void *aligned_alloc(size_t a, size_t n) {
if (IS2POW(a)) { if (IS2POW(a)) {

View file

@ -26,6 +26,7 @@
* portability, since that's guaranteed to work with all libraries * portability, since that's guaranteed to work with all libraries
* @return bytes written (excluding NUL) or -1 w/ errno * @return bytes written (excluding NUL) or -1 w/ errno
* @see xasprintf() for a better API * @see xasprintf() for a better API
* @threadsafe
*/ */
int(asprintf)(char **strp, const char *fmt, ...) { int(asprintf)(char **strp, const char *fmt, ...) {
int res; int res;

View file

@ -26,5 +26,6 @@
// @return rax is memory address, or NULL w/ errno // @return rax is memory address, or NULL w/ errno
// @note overreliance on memalign is a sure way to fragment space // @note overreliance on memalign is a sure way to fragment space
// @see dlcalloc() // @see dlcalloc()
// @threadsafe
calloc: jmp *hook_calloc(%rip) calloc: jmp *hook_calloc(%rip)
.endfn calloc,globl .endfn calloc,globl

View file

@ -28,5 +28,6 @@
// //
// @param rdi is allocation address, which may be NULL // @param rdi is allocation address, which may be NULL
// @see dlfree() // @see dlfree()
// @threadsafe
free: jmp *hook_free(%rip) free: jmp *hook_free(%rip)
.endfn free,globl .endfn free,globl

View file

@ -28,6 +28,7 @@
* that'll be returned. * that'll be returned.
* *
* @return pointer that must be free()'d, or NULL w/ errno * @return pointer that must be free()'d, or NULL w/ errno
* @threadsafe
*/ */
dontdiscard char *get_current_dir_name(void) { dontdiscard char *get_current_dir_name(void) {
const char *p; const char *p;

View file

@ -33,5 +33,6 @@
// //
// @param rdi is number of bytes needed, coerced to 1+ // @param rdi is number of bytes needed, coerced to 1+
// @return new memory, or NULL w/ errno // @return new memory, or NULL w/ errno
// @threadsafe
malloc: jmp *hook_malloc(%rip) malloc: jmp *hook_malloc(%rip)
.endfn malloc,globl .endfn malloc,globl

View file

@ -35,6 +35,7 @@
// @param rdi is address of allocation // @param rdi is address of allocation
// @return rax is total number of bytes // @return rax is total number of bytes
// @see dlmalloc_usable_size() // @see dlmalloc_usable_size()
// @threadsafe
malloc_usable_size: malloc_usable_size:
jmp *hook_malloc_usable_size(%rip) jmp *hook_malloc_usable_size(%rip)
.endfn malloc_usable_size,globl .endfn malloc_usable_size,globl

View file

@ -30,6 +30,7 @@
// @param rsi is number of bytes needed, coerced to 1+ // @param rsi is number of bytes needed, coerced to 1+
// @return rax is memory address, or NULL w/ errno // @return rax is memory address, or NULL w/ errno
// @see valloc(), pvalloc() // @see valloc(), pvalloc()
// @threadsafe
memalign: memalign:
jmp *hook_memalign(%rip) jmp *hook_memalign(%rip)
.endfn memalign,globl .endfn memalign,globl

View file

@ -35,6 +35,7 @@
* @param bytes is number of bytes to allocate * @param bytes is number of bytes to allocate
* @return return 0 or EINVAL or ENOMEM w/o setting errno * @return return 0 or EINVAL or ENOMEM w/o setting errno
* @see memalign() * @see memalign()
* @threadsafe
*/ */
int posix_memalign(void **pp, size_t alignment, size_t bytes) { int posix_memalign(void **pp, size_t alignment, size_t bytes) {
int e; int e;

View file

@ -25,6 +25,7 @@
* @param n number of bytes needed * @param n number of bytes needed
* @return memory address, or NULL w/ errno * @return memory address, or NULL w/ errno
* @see valloc() * @see valloc()
* @threadsafe
*/ */
void *pvalloc(size_t n) { void *pvalloc(size_t n) {
return memalign(PAGESIZE, ROUNDUP(n, PAGESIZE)); return memalign(PAGESIZE, ROUNDUP(n, PAGESIZE));

View file

@ -53,6 +53,7 @@
// @note realloc(p=0, n=0) → malloc(32) // @note realloc(p=0, n=0) → malloc(32)
// @note realloc(p≠0, n=0) → free(p) // @note realloc(p≠0, n=0) → free(p)
// @see dlrealloc() // @see dlrealloc()
// @threadsafe
realloc: realloc:
jmp *hook_realloc(%rip) jmp *hook_realloc(%rip)
.endfn realloc,globl .endfn realloc,globl

View file

@ -32,6 +32,7 @@
// @param rsi (newsize) is number of bytes needed // @param rsi (newsize) is number of bytes needed
// @return rax is result, or NULL w/ errno // @return rax is result, or NULL w/ errno
// @see dlrealloc_in_place() // @see dlrealloc_in_place()
// @threadsafe
realloc_in_place: realloc_in_place:
jmp *hook_realloc_in_place(%rip) jmp *hook_realloc_in_place(%rip)
.endfn realloc_in_place,globl .endfn realloc_in_place,globl

View file

@ -26,6 +26,7 @@
* @param ptr may be NULL for malloc() behavior * @param ptr may be NULL for malloc() behavior
* @param nmemb may be 0 for free() behavior; shrinking is promised too * @param nmemb may be 0 for free() behavior; shrinking is promised too
* @return new address or NULL w/ errno and ptr is NOT free()'d * @return new address or NULL w/ errno and ptr is NOT free()'d
* @threadsafe
*/ */
void *reallocarray(void *ptr, size_t nmemb, size_t itemsize) { void *reallocarray(void *ptr, size_t nmemb, size_t itemsize) {
size_t n; size_t n;

View file

@ -25,6 +25,7 @@
* @param s is a NUL-terminated byte string * @param s is a NUL-terminated byte string
* @return new string or NULL w/ errno * @return new string or NULL w/ errno
* @error ENOMEM * @error ENOMEM
* @threadsafe
*/ */
char *strdup(const char *s) { char *strdup(const char *s) {
size_t len = strlen(s); size_t len = strlen(s);

View file

@ -26,6 +26,7 @@
* @param n if less than strlen(s) will truncate the string * @param n if less than strlen(s) will truncate the string
* @return new string or NULL w/ errno * @return new string or NULL w/ errno
* @error ENOMEM * @error ENOMEM
* @threadsafe
*/ */
char *strndup(const char *s, size_t n) { char *strndup(const char *s, size_t n) {
char *s2; char *s2;

View file

@ -24,6 +24,7 @@
* @param n number of bytes needed * @param n number of bytes needed
* @return memory address, or NULL w/ errno * @return memory address, or NULL w/ errno
* @see pvalloc() * @see pvalloc()
* @threadsafe
*/ */
void *valloc(size_t n) { void *valloc(size_t n) {
return memalign(PAGESIZE, n); return memalign(PAGESIZE, n);

View file

@ -23,6 +23,7 @@
/** /**
* Formats string w/ dynamic memory allocation. * Formats string w/ dynamic memory allocation.
* @see xasprintf() for a better API * @see xasprintf() for a better API
* @threadsafe
*/ */
int(vasprintf)(char **strp, const char *fmt, va_list va) { int(vasprintf)(char **strp, const char *fmt, va_list va) {
va_list vb; va_list vb;

View file

@ -21,6 +21,7 @@
/** /**
* Allocates copy of wide string. * Allocates copy of wide string.
* @threadsafe
*/ */
wchar_t *wcsdup(const wchar_t *s) { wchar_t *wcsdup(const wchar_t *s) {
size_t len = wcslen(s); size_t len = wcslen(s);

View file

@ -70,7 +70,8 @@ static privileged inline void ReleaseFtraceLock(void) {
} }
static privileged inline bool AcquireFtraceLock(void) { static privileged inline bool AcquireFtraceLock(void) {
int me, owner, tries; int me, owner;
unsigned tries;
if (!__threaded) { if (!__threaded) {
return _cmpxchg(&ftrace_lock, 0, -1); return _cmpxchg(&ftrace_lock, 0, -1);
} else { } else {

View file

@ -19,7 +19,6 @@
#include "libc/assert.h" #include "libc/assert.h"
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/intrin/spinlock.h"
#include "libc/mem/mem.h" #include "libc/mem/mem.h"
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
#include "libc/stdio/internal.h" #include "libc/stdio/internal.h"

View file

@ -33,7 +33,7 @@
/** /**
* Blocks until data from stream buffer is written out. * Blocks until data from stream buffer is written out.
* *
* @param f is the stream handle * @param f is the stream handle, or 0 for all streams
* @return is 0 on success or -1 on error * @return is 0 on success or -1 on error
*/ */
int fflush_unlocked(FILE *f) { int fflush_unlocked(FILE *f) {

View file

@ -16,12 +16,28 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/intrin/spinlock.h" #include "libc/calls/calls.h"
#include "libc/intrin/cmpxchg.h"
#include "libc/intrin/lockcmpxchgp.h"
#include "libc/nexgen32e/threaded.h"
#include "libc/stdio/stdio.h" #include "libc/stdio/stdio.h"
/** /**
* Acquires lock on stdio object, blocking if needed. * Acquires reentrant lock on stdio object, blocking if needed.
*/ */
void flockfile(FILE *f) { void flockfile(FILE *f) {
_spinlock(&f->lock); int me, owner;
unsigned tries;
if (!__threaded) return;
for (tries = 0, me = gettid();;) {
owner = 0;
if (_lockcmpxchgp(&f->lock, &owner, me) || owner == me) {
return;
}
if (++tries & 7) {
__builtin_ia32_pause();
} else {
sched_yield();
}
}
} }

View file

@ -31,11 +31,11 @@ void _flushlbf(void) {
_spinlock(&__fflush.lock); _spinlock(&__fflush.lock);
for (i = 0; i < __fflush.handles.i; ++i) { for (i = 0; i < __fflush.handles.i; ++i) {
if ((f = __fflush.handles.p[i])) { if ((f = __fflush.handles.p[i])) {
_spinlock(&f->lock); flockfile(f);
if (f->bufmode == _IOLBF) { if (f->bufmode == _IOLBF) {
fflush_unlocked(f); fflush_unlocked(f);
} }
_spunlock(&f->lock); funlockfile(f);
} }
} }
_spunlock(&__fflush.lock); _spunlock(&__fflush.lock);

View file

@ -18,11 +18,17 @@
*/ */
#include "libc/stdio/stdio.h" #include "libc/stdio/stdio.h"
/**
* Formats and writes text to stream.
* @see printf() for further documentation
*/
int(fprintf)(FILE *f, const char *fmt, ...) { int(fprintf)(FILE *f, const char *fmt, ...) {
int rc; int rc;
va_list va; va_list va;
flockfile(f);
va_start(va, fmt); va_start(va, fmt);
rc = (vfprintf)(f, fmt, va); rc = (vfprintf_unlocked)(f, fmt, va);
va_end(va); va_end(va);
funlockfile(f);
return rc; return rc;
} }

View file

@ -0,0 +1,32 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=8 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2020 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/stdio/stdio.h"
/**
* Formats and writes text to stream.
* @see printf() for further documentation
*/
int(fprintf_unlocked)(FILE *f, const char *fmt, ...) {
int rc;
va_list va;
va_start(va, fmt);
rc = (vfprintf_unlocked)(f, fmt, va);
va_end(va);
return rc;
}

View file

@ -17,7 +17,6 @@
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/intrin/spinlock.h"
#include "libc/stdio/stdio.h" #include "libc/stdio/stdio.h"
#include "libc/sysv/consts/f.h" #include "libc/sysv/consts/f.h"
#include "libc/sysv/consts/fd.h" #include "libc/sysv/consts/fd.h"
@ -41,7 +40,7 @@ FILE *freopen(const char *pathname, const char *mode, FILE *stream) {
FILE *res; FILE *res;
unsigned flags; unsigned flags;
flags = fopenflags(mode); flags = fopenflags(mode);
_spinlock(&stream->lock); flockfile(stream);
fflush_unlocked(stream); fflush_unlocked(stream);
if (pathname) { if (pathname) {
/* open new stream, overwriting existing alloc */ /* open new stream, overwriting existing alloc */
@ -60,6 +59,6 @@ FILE *freopen(const char *pathname, const char *mode, FILE *stream) {
fcntl(stream->fd, F_SETFL, flags & ~O_CLOEXEC); fcntl(stream->fd, F_SETFL, flags & ~O_CLOEXEC);
res = stream; res = stream;
} }
_spunlock(&stream->lock); funlockfile(stream);
return res; return res;
} }

View file

@ -18,7 +18,6 @@
*/ */
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/intrin/spinlock.h"
#include "libc/stdio/internal.h" #include "libc/stdio/internal.h"
#include "libc/stdio/stdio.h" #include "libc/stdio/stdio.h"
#include "libc/sysv/consts/o.h" #include "libc/sysv/consts/o.h"
@ -36,11 +35,10 @@
* @param whence can be SEET_SET, SEEK_CUR, or SEEK_END * @param whence can be SEET_SET, SEEK_CUR, or SEEK_END
* @returns 0 on success or -1 on error * @returns 0 on success or -1 on error
*/ */
int fseeko(FILE *f, int64_t offset, int whence) { int fseeko_unlocked(FILE *f, int64_t offset, int whence) {
int res; int res;
ssize_t rc; ssize_t rc;
int64_t pos; int64_t pos;
_spinlock(&f->lock);
if (f->fd != -1) { if (f->fd != -1) {
if (__fflush_impl(f) == -1) return -1; if (__fflush_impl(f) == -1) return -1;
if (whence == SEEK_CUR && f->beg < f->end) { if (whence == SEEK_CUR && f->beg < f->end) {
@ -77,6 +75,5 @@ int fseeko(FILE *f, int64_t offset, int whence) {
res = -1; res = -1;
} }
} }
_spunlock(&f->lock);
return res; return res;
} }

View file

@ -18,7 +18,6 @@
*/ */
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/intrin/spinlock.h"
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
#include "libc/stdio/internal.h" #include "libc/stdio/internal.h"
#include "libc/stdio/stdio.h" #include "libc/stdio/stdio.h"
@ -49,8 +48,8 @@ static int64_t ftello_unlocked(FILE *f) {
*/ */
int64_t ftello(FILE *f) { int64_t ftello(FILE *f) {
int64_t rc; int64_t rc;
_spinlock(&f->lock); flockfile(f);
rc = ftello_unlocked(f); rc = ftello_unlocked(f);
_spunlock(&f->lock); funlockfile(f);
return rc; return rc;
} }

View file

@ -16,13 +16,23 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/intrin/spinlock.h" #include "libc/calls/calls.h"
#include "libc/intrin/lockcmpxchgp.h"
#include "libc/nexgen32e/threaded.h"
#include "libc/stdio/stdio.h" #include "libc/stdio/stdio.h"
/** /**
* Tries to acquire stdio object lock. * Tries to acquire reentrant stdio object lock.
* @return 0 for success or non-zero if someone else has the lock *
* @return 0 on success, or non-zero if another thread owns the lock
*/ */
int ftrylockfile(FILE *f) { int ftrylockfile(FILE *f) {
return _trylock(&f->lock); int me, owner = 0;
if (__threaded) {
me = gettid();
if (!_lockcmpxchgp(&f->lock, &owner, me) && owner == me) {
owner = 0;
}
}
return owner;
} }

View file

@ -19,7 +19,6 @@
#include "libc/assert.h" #include "libc/assert.h"
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/intrin/spinlock.h"
#include "libc/macros.internal.h" #include "libc/macros.internal.h"
#include "libc/mem/mem.h" #include "libc/mem/mem.h"
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
@ -87,8 +86,8 @@ static ssize_t getdelim_unlocked(char **s, size_t *n, int delim, FILE *f) {
*/ */
ssize_t getdelim(char **s, size_t *n, int delim, FILE *f) { ssize_t getdelim(char **s, size_t *n, int delim, FILE *f) {
ssize_t rc; ssize_t rc;
_spinlock(&f->lock); flockfile(f);
rc = getdelim_unlocked(s, n, delim, f); rc = getdelim_unlocked(s, n, delim, f);
_spunlock(&f->lock); funlockfile(f);
return rc; return rc;
} }

View file

@ -19,7 +19,7 @@
#include "libc/stdio/stdio.h" #include "libc/stdio/stdio.h"
/** /**
* Formats and writes string to stdout. * Formats and writes text to stdout.
* *
* Cosmopolitan supports most of the standard formatting behaviors * Cosmopolitan supports most of the standard formatting behaviors
* described by `man 3 printf`, in addition to the following * described by `man 3 printf`, in addition to the following

View file

@ -18,21 +18,32 @@
*/ */
#include "libc/stdio/stdio.h" #include "libc/stdio/stdio.h"
/** static int PutsImpl(const char *s, FILE *f) {
* Writes string w/ trailing newline to stdout.
*/
int puts(const char *s) {
FILE *f;
size_t n, r; size_t n, r;
f = stdout;
if ((n = strlen(s))) { if ((n = strlen(s))) {
r = fwrite(s, 1, n, f); r = fwrite_unlocked(s, 1, n, f);
if (!r) return -1; if (!r) return -1;
if (r < n) return r; if (r < n) return r;
} }
if (fputc('\n', f) == -1) { if (fputc_unlocked('\n', f) == -1) {
if (feof(f)) return n; if (feof_unlocked(f)) return n;
return -1; return -1;
} }
return n + 1; return n + 1;
} }
/**
* Writes string w/ trailing newline to stdout.
*
* @return non-negative number on success, or `EOF` on error with
* `errno` set and the `ferror(stdout)` state is updated
*/
int puts(const char *s) {
FILE *f;
int bytes;
f = stdout;
flockfile(f);
bytes = PutsImpl(s, f);
funlockfile(f);
return bytes;
}

View file

@ -26,6 +26,9 @@
* EOF state, without reopening it. * EOF state, without reopening it.
*/ */
void rewind(FILE *f) { void rewind(FILE *f) {
fseek(f, 0, SEEK_SET); flockfile(f);
f->state = 0; if (!fseeko_unlocked(f, 0, SEEK_SET)) {
f->state = 0;
}
funlockfile(f);
} }

View file

@ -30,6 +30,7 @@
* @return 0 on success or -1 on error * @return 0 on success or -1 on error
*/ */
int setvbuf(FILE *f, char *buf, int mode, size_t size) { int setvbuf(FILE *f, char *buf, int mode, size_t size) {
flockfile(f);
if (buf) { if (buf) {
if (!size) size = BUFSIZ; if (!size) size = BUFSIZ;
if (!f->nofree && f->buf != buf) free_s(&f->buf); if (!f->nofree && f->buf != buf) free_s(&f->buf);
@ -38,5 +39,6 @@ int setvbuf(FILE *f, char *buf, int mode, size_t size) {
f->nofree = true; f->nofree = true;
} }
f->bufmode = mode; f->bufmode = mode;
funlockfile(f);
return 0; return 0;
} }

View file

@ -116,30 +116,6 @@ int wprintf(const wchar_t *, ...);
int wscanf(const wchar_t *, ...); int wscanf(const wchar_t *, ...);
int fwide(FILE *, int); int fwide(FILE *, int);
/*───────────────────────────────────────────────────────────────────────────│─╗
cosmopolitan § standard i/o » optimizations
*/
#define getc(f) fgetc(f)
#define getwc(f) fgetwc(f)
#define putc(c, f) fputc(c, f)
#define putwc(c, f) fputwc(c, f)
#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
#define printf(FMT, ...) (printf)(PFLINK(FMT), ##__VA_ARGS__)
#define vprintf(FMT, VA) (vprintf)(PFLINK(FMT), VA)
#define fprintf(F, FMT, ...) (fprintf)(F, PFLINK(FMT), ##__VA_ARGS__)
#define vfprintf(F, FMT, VA) (vfprintf)(F, PFLINK(FMT), VA)
#define vscanf(FMT, VA) (vscanf)(SFLINK(FMT), VA)
#define scanf(FMT, ...) (scanf)(SFLINK(FMT), ##__VA_ARGS__)
#define fscanf(F, FMT, ...) (fscanf)(F, SFLINK(FMT), ##__VA_ARGS__)
#define vfscanf(F, FMT, VA) (vfscanf)(F, SFLINK(FMT), VA)
#endif
#define stdin SYMBOLIC(stdin)
#define stdout SYMBOLIC(stdout)
#define stderr SYMBOLIC(stderr)
/*───────────────────────────────────────────────────────────────────────────│─╗ /*───────────────────────────────────────────────────────────────────────────│─╗
cosmopolitan § standard i/o » without mutexes cosmopolitan § standard i/o » without mutexes
*/ */
@ -172,12 +148,45 @@ wchar_t *fgetws_unlocked(wchar_t *, int, FILE *);
int fputws_unlocked(const wchar_t *, FILE *); int fputws_unlocked(const wchar_t *, FILE *);
wint_t ungetwc_unlocked(wint_t, FILE *) paramsnonnull(); wint_t ungetwc_unlocked(wint_t, FILE *) paramsnonnull();
int ungetc_unlocked(int, FILE *) paramsnonnull(); int ungetc_unlocked(int, FILE *) paramsnonnull();
int fseeko_unlocked(FILE *, int64_t, int) paramsnonnull();
int fprintf_unlocked(FILE *, const char *, ...) printfesque(2)
paramsnonnull((1, 2)) dontthrow nocallback;
int vfprintf_unlocked(FILE *, const char *, va_list)
paramsnonnull() dontthrow nocallback;
#define getc_unlocked(f) fgetc_unlocked(f) #define getc_unlocked(f) fgetc_unlocked(f)
#define getwc_unlocked(f) fgetwc_unlocked(f) #define getwc_unlocked(f) fgetwc_unlocked(f)
#define putc_unlocked(c, f) fputc_unlocked(c, f) #define putc_unlocked(c, f) fputc_unlocked(c, f)
#define putwc_unlocked(c, f) fputwc_unlocked(c, f) #define putwc_unlocked(c, f) fputwc_unlocked(c, f)
/*───────────────────────────────────────────────────────────────────────────│─╗
cosmopolitan § standard i/o » optimizations
*/
#define getc(f) fgetc(f)
#define getwc(f) fgetwc(f)
#define putc(c, f) fputc(c, f)
#define putwc(c, f) fputwc(c, f)
#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
/* clang-format off */
#define printf(FMT, ...) (printf)(PFLINK(FMT), ##__VA_ARGS__)
#define vprintf(FMT, VA) (vprintf)(PFLINK(FMT), VA)
#define fprintf(F, FMT, ...) (fprintf)(F, PFLINK(FMT), ##__VA_ARGS__)
#define vfprintf(F, FMT, VA) (vfprintf)(F, PFLINK(FMT), VA)
#define fprintf_unlocked(F, FMT, ...) (fprintf_unlocked)(F, PFLINK(FMT), ##__VA_ARGS__)
#define vfprintf_unlocked(F, FMT, VA) (vfprintf_unlocked)(F, PFLINK(FMT), VA)
#define vscanf(FMT, VA) (vscanf)(SFLINK(FMT), VA)
#define scanf(FMT, ...) (scanf)(SFLINK(FMT), ##__VA_ARGS__)
#define fscanf(F, FMT, ...) (fscanf)(F, SFLINK(FMT), ##__VA_ARGS__)
#define vfscanf(F, FMT, VA) (vfscanf)(F, SFLINK(FMT), VA)
/* clang-format on */
#endif
#define stdin SYMBOLIC(stdin)
#define stdout SYMBOLIC(stdout)
#define stderr SYMBOLIC(stderr)
COSMOPOLITAN_C_END_ COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_STDIO_STDIO_H_ */ #endif /* COSMOPOLITAN_LIBC_STDIO_STDIO_H_ */

View file

@ -22,6 +22,7 @@
// //
// @param rdi has stream pointer // @param rdi has stream pointer
// @see clearerr_unlocked() // @see clearerr_unlocked()
// @threadsafe
clearerr: clearerr:
mov %rdi,%r11 mov %rdi,%r11
ezlea clearerr_unlocked,ax ezlea clearerr_unlocked,ax

View file

@ -23,6 +23,7 @@
// @param rdi has file stream object pointer // @param rdi has file stream object pointer
// @note EOF doesn't count // @note EOF doesn't count
// @see feof_unlocked() // @see feof_unlocked()
// @threadsafe
feof: mov %rdi,%r11 feof: mov %rdi,%r11
ezlea feof_unlocked,ax ezlea feof_unlocked,ax
jmp stdio_unlock jmp stdio_unlock

View file

@ -23,6 +23,7 @@
// @param rdi has file stream object pointer // @param rdi has file stream object pointer
// @note EOF doesn't count // @note EOF doesn't count
// @see ferror_unlocked() // @see ferror_unlocked()
// @threadsafe
ferror: mov %rdi,%r11 ferror: mov %rdi,%r11
ezlea ferror_unlocked,ax ezlea ferror_unlocked,ax
jmp stdio_unlock jmp stdio_unlock

View file

@ -20,10 +20,14 @@
// Blocks until data from stream buffer is written out. // Blocks until data from stream buffer is written out.
// //
// @param rdi is the stream handle // @param rdi is the stream handle, or 0 for all streams
// @return 0 on success or -1 w/ errno // @return 0 on success or -1 w/ errno
// @see fflush_unlocked() // @see fflush_unlocked()
fflush: mov %rdi,%r11 // @threadsafe
ezlea fflush_unlocked,ax fflush: ezlea fflush_unlocked,ax
test %rdi,%rdi
jz 1f
mov %rdi,%r11
jmp stdio_unlock jmp stdio_unlock
1: jmp *%rax
.endfn fflush,globl .endfn fflush,globl

View file

@ -23,6 +23,7 @@
// @param rdi has stream object pointer // @param rdi has stream object pointer
// @return byte in range 0..255, or -1 w/ errno // @return byte in range 0..255, or -1 w/ errno
// @see fgetc_unlocked() // @see fgetc_unlocked()
// @threadsafe
fgetc: mov %rdi,%r11 fgetc: mov %rdi,%r11
ezlea fgetc_unlocked,ax ezlea fgetc_unlocked,ax
jmp stdio_unlock jmp stdio_unlock

View file

@ -30,6 +30,7 @@
// @return rax has rdi on success, NULL on error or // @return rax has rdi on success, NULL on error or
// NULL if EOF happens with zero chars read // NULL if EOF happens with zero chars read
// @see fgets_unlocked() // @see fgets_unlocked()
// @threadsafe
fgets: mov %rdx,%r11 fgets: mov %rdx,%r11
ezlea fgets_unlocked,ax ezlea fgets_unlocked,ax
jmp stdio_unlock jmp stdio_unlock

View file

@ -23,6 +23,7 @@
// @param rdi has stream object pointer // @param rdi has stream object pointer
// @return wide character or -1 on EOF or error // @return wide character or -1 on EOF or error
// @see fgetwc_unlocked() // @see fgetwc_unlocked()
// @threadsafe
fgetwc: mov %rdi,%r11 fgetwc: mov %rdi,%r11
ezlea fgetwc_unlocked,ax ezlea fgetwc_unlocked,ax
jmp stdio_unlock jmp stdio_unlock

View file

@ -28,6 +28,7 @@
// @param rsi is size of rdi buffer // @param rsi is size of rdi buffer
// @param rsi is file stream object pointer // @param rsi is file stream object pointer
// @see fgetws_unlocked() // @see fgetws_unlocked()
// @threadsafe
fgetws: mov %rdx,%r11 fgetws: mov %rdx,%r11
ezlea fgetws_unlocked,ax ezlea fgetws_unlocked,ax
jmp stdio_unlock jmp stdio_unlock

View file

@ -22,6 +22,7 @@
// //
// @param rdi has file stream object pointer // @param rdi has file stream object pointer
// @see fileno_unlocked() // @see fileno_unlocked()
// @threadsafe
fileno: mov %rdi,%r11 fileno: mov %rdi,%r11
ezlea fileno_unlocked,ax ezlea fileno_unlocked,ax
jmp stdio_unlock jmp stdio_unlock

View file

@ -24,6 +24,7 @@
// @param rsi has stream object pointer // @param rsi has stream object pointer
// @return c as unsigned char if written or -1 w/ errno // @return c as unsigned char if written or -1 w/ errno
// @see fputc_unlocked() // @see fputc_unlocked()
// @threadsafe
fputc: mov %rsi,%r11 fputc: mov %rsi,%r11
ezlea fputc_unlocked,ax ezlea fputc_unlocked,ax
jmp stdio_unlock jmp stdio_unlock

View file

@ -28,6 +28,7 @@
// @param rsi is file object stream pointer // @param rsi is file object stream pointer
// @return strlen(rdi) on success or -1 w/ errno // @return strlen(rdi) on success or -1 w/ errno
// @see fputs_unlocked() // @see fputs_unlocked()
// @threadsafe
fputs: mov %rsi,%r11 fputs: mov %rsi,%r11
ezlea fputs_unlocked,ax ezlea fputs_unlocked,ax
jmp stdio_unlock jmp stdio_unlock

View file

@ -24,6 +24,7 @@
// @param rsi has file object stream pointer // @param rsi has file object stream pointer
// @return rax is wide character if written or -1 w/ errno // @return rax is wide character if written or -1 w/ errno
// @see fputwc_unlocked() // @see fputwc_unlocked()
// @threadsafe
fputwc: mov %rsi,%r11 fputwc: mov %rsi,%r11
ezlea fputwc_unlocked,ax ezlea fputwc_unlocked,ax
jmp stdio_unlock jmp stdio_unlock

View file

@ -28,6 +28,7 @@
// @param rsi is file object stream pointer // @param rsi is file object stream pointer
// @return strlen(rdi) on success or -1 w/ errno // @return strlen(rdi) on success or -1 w/ errno
// @see fputws_unlocked() // @see fputws_unlocked()
// @threadsafe
fputws: mov %rsi,%r11 fputws: mov %rsi,%r11
ezlea fputws_unlocked,ax ezlea fputws_unlocked,ax
jmp stdio_unlock jmp stdio_unlock

View file

@ -26,6 +26,7 @@
// @param rcx has file object stream pointer // @param rcx has file object stream pointer
// @return count on success, [0,count) on EOF, 0 on error or count==0 // @return count on success, [0,count) on EOF, 0 on error or count==0
// @see fread_unlocked() // @see fread_unlocked()
// @threadsafe
fread: mov %rcx,%r11 fread: mov %rcx,%r11
ezlea fread_unlocked,ax ezlea fread_unlocked,ax
jmp stdio_unlock jmp stdio_unlock

View file

@ -0,0 +1,37 @@
/*-*- 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 2020 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/macros.internal.h"
// Repositions open file stream.
//
// This function flushes the buffer (unless it's currently in the EOF
// state) and then calls lseek() on the underlying file. If the stream
// is in the EOF state, this function can be used to restore it without
// needing to reopen the file.
//
// @param rdi is stream handle
// @param rsi is offset is the byte delta
// @param rdx is whence and can be SEET_SET, SEEK_CUR, or SEEK_END
// @return 0 on success or -1 w/ errno
// @see fflush_unlocked()
// @threadsafe
fseeko: mov %rdi,%r11
ezlea fseeko_unlocked,ax
jmp stdio_unlock
.endfn fseeko,globl

View file

@ -26,6 +26,7 @@
// @param rcx has file object stream pointer // @param rcx has file object stream pointer
// @return count on success, [0,count) on EOF, 0 on error or count==0 // @return count on success, [0,count) on EOF, 0 on error or count==0
// @see fwrite_unlocked() // @see fwrite_unlocked()
// @threadsafe
fwrite: mov %rcx,%r11 fwrite: mov %rcx,%r11
ezlea fwrite_unlocked,ax ezlea fwrite_unlocked,ax
jmp stdio_unlock jmp stdio_unlock

View file

@ -23,6 +23,7 @@
// @param rdi has file stream object pointer // @param rdi has file stream object pointer
// @return byte in range 0..255, or -1 w/ errno // @return byte in range 0..255, or -1 w/ errno
// @see fgetc_unlocked() // @see fgetc_unlocked()
// @threadsafe
getc: mov %rdi,%r11 getc: mov %rdi,%r11
ezlea fgetwc_unlocked,ax ezlea fgetwc_unlocked,ax
jmp stdio_unlock jmp stdio_unlock

View file

@ -22,6 +22,7 @@
// //
// @return byte in range 0..255, or -1 w/ errno // @return byte in range 0..255, or -1 w/ errno
// @see fgetc_unlocked() // @see fgetc_unlocked()
// @threadsafe
getchar: getchar:
mov stdin(%rip),%rdi mov stdin(%rip),%rdi
mov %rdi,%r11 mov %rdi,%r11

View file

@ -23,6 +23,7 @@
// @param rdi has file stream object pointer // @param rdi has file stream object pointer
// @return wide character or -1 on EOF or error // @return wide character or -1 on EOF or error
// @see fgetwc_unlocked() // @see fgetwc_unlocked()
// @threadsafe
getwc: mov %rdi,%r11 getwc: mov %rdi,%r11
ezlea fgetwc_unlocked,ax ezlea fgetwc_unlocked,ax
jmp stdio_unlock jmp stdio_unlock

View file

@ -22,6 +22,7 @@
// //
// @return wide character or -1 on EOF or error // @return wide character or -1 on EOF or error
// @see fgetwc_unlocked() // @see fgetwc_unlocked()
// @threadsafe
getwchar: getwchar:
mov stdin(%rip),%rdi mov stdin(%rip),%rdi
mov %rdi,%r11 mov %rdi,%r11

View file

@ -24,6 +24,7 @@
// @param rsi has stream object pointer // @param rsi has stream object pointer
// @return c as unsigned char if written or -1 w/ errno // @return c as unsigned char if written or -1 w/ errno
// @see fputc_unlocked() // @see fputc_unlocked()
// @threadsafe
putc: mov %rsi,%r11 putc: mov %rsi,%r11
ezlea fputc_unlocked,ax ezlea fputc_unlocked,ax
jmp stdio_unlock jmp stdio_unlock

View file

@ -23,6 +23,7 @@
// @param rdi has character // @param rdi has character
// @return c (as unsigned char) if written or -1 w/ errno // @return c (as unsigned char) if written or -1 w/ errno
// @see fputc_unlocked() // @see fputc_unlocked()
// @threadsafe
putchar: putchar:
mov stdout(%rip),%rsi mov stdout(%rip),%rsi
mov %rsi,%r11 mov %rsi,%r11

View file

@ -24,6 +24,7 @@
// @param rsi has file object // @param rsi has file object
// @return wc if written or -1 w/ errno // @return wc if written or -1 w/ errno
// @see putwc_unlocked() // @see putwc_unlocked()
// @threadsafe
putwc: mov %rsi,%r11 putwc: mov %rsi,%r11
ezlea fputwc_unlocked,ax ezlea fputwc_unlocked,ax
jmp stdio_unlock jmp stdio_unlock

View file

@ -23,6 +23,7 @@
// @param rdi has wide character // @param rdi has wide character
// @return wc if written or -1 w/ errno // @return wc if written or -1 w/ errno
// @see fputwc_unlocked() // @see fputwc_unlocked()
// @threadsafe
putwchar: putwchar:
mov stdout(%rip),%rsi mov stdout(%rip),%rsi
mov %rsi,%r11 mov %rsi,%r11

View file

@ -18,53 +18,54 @@
*/ */
#include "libc/macros.internal.h" #include "libc/macros.internal.h"
#define LOCK 0x2c /* see struct file in stdio.h */
// Wrapper for applying locking to stdio functions. // Wrapper for applying locking to stdio functions.
// //
// This function is intended to be called by thunks. // This function is intended to be called by thunks.
// //
// @param rax has the delegate function pointer // @param rax is stdio function pointer
// @param rdi is passed along as an arg // @param rdi is passed along as an arg
// @param rsi is passed along as an arg // @param rsi is passed along as an arg
// @param rdx is passed along as an arg // @param rdx is passed along as an arg
// @param rcx 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 // @param r11 has the FILE* obj pointer
// @return rax is passed along as result // @return rax is passed along as result
// @return rdx is passed along as result // @return rdx is passed along as result
// @threadsafe
stdio_unlock: stdio_unlock:
push %rbp push %rbp
mov %rsp,%rbp mov %rsp,%rbp
.profilable .profilable
// acquires mutex // acquires mutex
push %rcx push %rax
push %rdi
push %rsi
push %rdx push %rdx
mov $1,%cl push %rcx
0: mov LOCK(%r11),%dl # optimistic push %r11
test %dl,%dl mov %r11,%rdi
je 2f call flockfile
1: pause # hyperyield pop %r11
jmp 0b
2: mov %ecx,%edx
xchg LOCK(%r11),%dl # locks bus!
test %dl,%dl
jne 1b
pop %rdx
pop %rcx pop %rcx
pop %rdx
pop %rsi
pop %rdi
pop %rax
// calls delegate // calls delegate
push %rsi
push %r11 push %r11
push %rsi # align stack
call *%rax call *%rax
pop %r11
pop %rsi pop %rsi
pop %r11
// releases mutex // releases mutex
movb $0,LOCK(%r11) push %rax
push %rdx
mov %r11,%rdi
call funlockfile
pop %rdx
pop %rax
pop %rbp pop %rbp
ret ret

View file

@ -24,6 +24,7 @@
// @param rds has stream object pointer // @param rds has stream object pointer
// @return rax has rdi on success or -1 w/ errno // @return rax has rdi on success or -1 w/ errno
// @see ungetc_unlocked() // @see ungetc_unlocked()
// @threadsafe
ungetc: mov %rsi,%r11 ungetc: mov %rsi,%r11
ezlea ungetc_unlocked,ax ezlea ungetc_unlocked,ax
jmp stdio_unlock jmp stdio_unlock

View file

@ -24,6 +24,7 @@
// @param rds has stream object pointer // @param rds has stream object pointer
// @return rax has rdi on success or -1 w/ errno // @return rax has rdi on success or -1 w/ errno
// @see ungetwc_unlocked() // @see ungetwc_unlocked()
// @threadsafe
ungetwc: ungetwc:
mov %rsi,%r11 mov %rsi,%r11
ezlea ungetwc_unlocked,ax ezlea ungetwc_unlocked,ax

View file

@ -16,45 +16,16 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#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/stdio/stdio.h"
#include "libc/sysv/errfuns.h"
struct state {
FILE *f;
int n;
};
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;
t->n += n;
rc = 0;
} else if (!fwrite_unlocked(s, 1, n, t->f)) {
rc = -1;
} else {
t->n += n;
rc = 0;
}
_spunlock(&t->f->lock);
} else {
rc = 0;
}
return 0;
}
/**
* Formats and writes text to stream.
* @see printf() for further documentation
*/
int(vfprintf)(FILE *f, const char *fmt, va_list va) { int(vfprintf)(FILE *f, const char *fmt, va_list va) {
struct state st[1] = {{f, 0}}; int rc;
if (__fmt(vfprintfputchar, st, fmt, va) != -1) { flockfile(f);
return st->n; rc = (vfprintf_unlocked)(f, fmt, va);
} else { funlockfile(f);
return -1; return rc;
}
} }

View file

@ -0,0 +1,61 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=8 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2020 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/fmt/fmt.h"
#include "libc/limits.h"
#include "libc/stdio/stdio.h"
#include "libc/sysv/errfuns.h"
struct state {
FILE *f;
int n;
};
static int vfprintfputchar(const char *s, struct state *t, size_t n) {
int rc;
if (n) {
if (n == 1 && *s != '\n' && t->f->beg < t->f->size &&
t->f->bufmode != _IONBF) {
t->f->buf[t->f->beg++] = *s;
t->n += n;
rc = 0;
} else if (!fwrite_unlocked(s, 1, n, t->f)) {
rc = -1;
} else {
t->n += n;
rc = 0;
}
} else {
rc = 0;
}
return 0;
}
/**
* Formats and writes text to stream.
* @see printf() for further documentation
*/
int(vfprintf_unlocked)(FILE *f, const char *fmt, va_list va) {
int rc;
struct state st[1] = {{f, 0}};
if ((rc = __fmt(vfprintfputchar, st, fmt, va)) != -1) {
rc = st->n;
}
return rc;
}

View file

@ -18,6 +18,10 @@
*/ */
#include "libc/stdio/stdio.h" #include "libc/stdio/stdio.h"
/**
* Formats and writes text to stdout.
* @see printf() for further documentation
*/
int(vprintf)(const char* fmt, va_list va) { int(vprintf)(const char* fmt, va_list va) {
return (vfprintf)(stdout, fmt, va); return (vfprintf)(stdout, fmt, va);
} }

View file

@ -18,6 +18,7 @@
*/ */
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/stdio/stdio.h" #include "libc/stdio/stdio.h"
#include "libc/testlib/ezbench.h"
#include "libc/testlib/testlib.h" #include "libc/testlib/testlib.h"
FILE *f; FILE *f;
@ -60,3 +61,13 @@ TEST(fgetc, testUnbuffered) {
EXPECT_TRUE(feof(f)); EXPECT_TRUE(feof(f));
EXPECT_NE(-1, fclose(f)); EXPECT_NE(-1, fclose(f));
} }
BENCH(fputc, bench) {
FILE *f;
ASSERT_NE(NULL, (f = fopen("/dev/null", "w")));
EZBENCH2("fputc", donothing, fputc('E', f));
flockfile(f);
EZBENCH2("fputc_unlocked", donothing, fputc_unlocked('E', f));
funlockfile(f);
fclose(f);
}

View file

@ -12,20 +12,6 @@
COSMOPOLITAN_C_START_ COSMOPOLITAN_C_START_
/* clang-format off */ /* clang-format off */
#define MBEDTLS_ERR_NET_SOCKET_FAILED -0x0042 /*< Failed to open a socket. */
#define MBEDTLS_ERR_NET_CONNECT_FAILED -0x0044 /*< The connection to the given server / port failed. */
#define MBEDTLS_ERR_NET_BIND_FAILED -0x0046 /*< Binding of the socket failed. */
#define MBEDTLS_ERR_NET_LISTEN_FAILED -0x0048 /*< Could not listen on the socket. */
#define MBEDTLS_ERR_NET_ACCEPT_FAILED -0x004A /*< Could not accept the incoming connection. */
#define MBEDTLS_ERR_NET_RECV_FAILED -0x004C /*< Reading information from the socket failed. */
#define MBEDTLS_ERR_NET_SEND_FAILED -0x004E /*< Sending information through the socket failed. */
#define MBEDTLS_ERR_NET_CONN_RESET -0x0050 /*< Connection was reset by peer. */
#define MBEDTLS_ERR_NET_UNKNOWN_HOST -0x0052 /*< Failed to get an IP address for the given hostname. */
#define MBEDTLS_ERR_NET_BUFFER_TOO_SMALL -0x0043 /*< Buffer is too small to hold the data. */
#define MBEDTLS_ERR_NET_INVALID_CONTEXT -0x0045 /*< The context is invalid, eg because it was free()ed. */
#define MBEDTLS_ERR_NET_POLL_FAILED -0x0047 /*< Polling the net context failed. */
#define MBEDTLS_ERR_NET_BAD_INPUT_DATA -0x0049 /*< Input invalid. */
/* /*
* SSL Error codes * SSL Error codes
*/ */

View file

@ -29,6 +29,7 @@
#include "third_party/mbedtls/ctr_drbg.h" #include "third_party/mbedtls/ctr_drbg.h"
#include "third_party/mbedtls/ecp.h" #include "third_party/mbedtls/ecp.h"
#include "third_party/mbedtls/error.h" #include "third_party/mbedtls/error.h"
#include "third_party/mbedtls/net_sockets.h"
#include "third_party/mbedtls/platform.h" #include "third_party/mbedtls/platform.h"
#include "third_party/mbedtls/ssl.h" #include "third_party/mbedtls/ssl.h"
#include "tool/build/lib/eztls.h" #include "tool/build/lib/eztls.h"

View file

@ -51,6 +51,7 @@
#include "libc/time/time.h" #include "libc/time/time.h"
#include "libc/x/x.h" #include "libc/x/x.h"
#include "net/https/https.h" #include "net/https/https.h"
#include "third_party/mbedtls/net_sockets.h"
#include "third_party/mbedtls/ssl.h" #include "third_party/mbedtls/ssl.h"
#include "third_party/zlib/zlib.h" #include "third_party/zlib/zlib.h"
#include "tool/build/lib/eztls.h" #include "tool/build/lib/eztls.h"
@ -112,6 +113,7 @@ static const struct addrinfo kResolvHints = {.ai_family = AF_INET,
int g_sock; int g_sock;
char *g_prog; char *g_prog;
long g_backoff;
char *g_runitd; char *g_runitd;
jmp_buf g_jmpbuf; jmp_buf g_jmpbuf;
uint16_t g_sshport; uint16_t g_sshport;
@ -308,7 +310,7 @@ TryAgain:
freeaddrinfo(ai); freeaddrinfo(ai);
} }
static void Send(const void *output, size_t outputsize) { static bool Send(const void *output, size_t outputsize) {
int rc, have; int rc, have;
static bool once; static bool once;
static z_stream zs; static z_stream zs;
@ -326,11 +328,17 @@ static void Send(const void *output, size_t outputsize) {
rc = deflate(&zs, Z_SYNC_FLUSH); rc = deflate(&zs, Z_SYNC_FLUSH);
CHECK_NE(Z_STREAM_ERROR, rc); CHECK_NE(Z_STREAM_ERROR, rc);
have = sizeof(zbuf) - zs.avail_out; have = sizeof(zbuf) - zs.avail_out;
CHECK_EQ(have, mbedtls_ssl_write(&ezssl, zbuf, have)); rc = mbedtls_ssl_write(&ezssl, zbuf, have);
if (rc == MBEDTLS_ERR_NET_CONN_RESET) {
usleep((g_backoff = (g_backoff + 1000) * 2));
return false;
}
CHECK_EQ(have, rc);
} while (!zs.avail_out); } while (!zs.avail_out);
return true;
} }
void SendRequest(void) { int SendRequest(void) {
int fd; int fd;
char *p; char *p;
size_t i; size_t i;
@ -357,22 +365,30 @@ void SendRequest(void) {
q = mempcpy(q, name, namesize); q = mempcpy(q, name, namesize);
assert(hdrsize == q - hdr); assert(hdrsize == q - hdr);
DEBUGF("running %s on %s", g_prog, g_hostname); DEBUGF("running %s on %s", g_prog, g_hostname);
Send(hdr, hdrsize); if (Send(hdr, hdrsize) && Send(p, progsize)) {
Send(p, progsize); if (!(rc = EzTlsFlush(&ezbio, 0, 0))) {
CHECK_EQ(0, EzTlsFlush(&ezbio, 0, 0)); rc = 0;
} else if (rc == MBEDTLS_ERR_NET_CONN_RESET) {
rc = -1;
} else {
CHECK_EQ(0, rc);
}
} else {
rc = -1;
}
CHECK_NE(-1, munmap(p, st.st_size)); CHECK_NE(-1, munmap(p, st.st_size));
CHECK_NE(-1, close(fd)); CHECK_NE(-1, close(fd));
return rc;
} }
bool Recv(unsigned char *p, size_t n) { bool Recv(unsigned char *p, size_t n) {
size_t i, rc; size_t i, rc;
static long backoff;
for (i = 0; i < n; i += rc) { for (i = 0; i < n; i += rc) {
do { do {
rc = mbedtls_ssl_read(&ezssl, p + i, n - i); rc = mbedtls_ssl_read(&ezssl, p + i, n - i);
} while (rc == MBEDTLS_ERR_SSL_WANT_READ); } while (rc == MBEDTLS_ERR_SSL_WANT_READ);
if (!rc || rc == MBEDTLS_ERR_NET_CONN_RESET) { if (!rc || rc == MBEDTLS_ERR_NET_CONN_RESET) {
usleep((backoff = (backoff + 1000) * 2)); usleep((g_backoff = (g_backoff + 1000) * 2));
return false; return false;
} else if (rc < 0) { } else if (rc < 0) {
TlsDie("read response failed", rc); TlsDie("read response failed", rc);
@ -442,8 +458,7 @@ int RunOnHost(char *spec) {
WARNF("warning: got connection reset in handshake"); WARNF("warning: got connection reset in handshake");
close(g_sock); close(g_sock);
} }
SendRequest(); } while ((rc = SendRequest()) == -1 || (rc = ReadResponse()) == -1);
} while ((rc = ReadResponse()) == -1);
return rc; return rc;
} }

View file

@ -42,6 +42,7 @@
#include "libc/sysv/consts/o.h" #include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/poll.h" #include "libc/sysv/consts/poll.h"
#include "libc/sysv/consts/sa.h" #include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/consts/so.h" #include "libc/sysv/consts/so.h"
#include "libc/sysv/consts/sock.h" #include "libc/sysv/consts/sock.h"
#include "libc/sysv/consts/sol.h" #include "libc/sysv/consts/sol.h"
@ -110,7 +111,12 @@ void OnInterrupt(int sig) {
void OnChildTerminated(int sig) { void OnChildTerminated(int sig) {
int ws, pid; int ws, pid;
sigset_t ss, oldss;
sigfillset(&ss);
sigdelset(&ss, SIGTERM);
sigprocmask(SIG_BLOCK, &ss, &oldss);
for (;;) { for (;;) {
INFOF("waitpid");
if ((pid = waitpid(-1, &ws, WNOHANG)) != -1) { if ((pid = waitpid(-1, &ws, WNOHANG)) != -1) {
if (pid) { if (pid) {
if (WIFEXITED(ws)) { if (WIFEXITED(ws)) {
@ -127,6 +133,7 @@ void OnChildTerminated(int sig) {
FATALF("waitpid failed in sigchld"); FATALF("waitpid failed in sigchld");
} }
} }
sigprocmask(SIG_SETMASK, &oldss, 0);
} }
wontreturn void ShowUsage(FILE *f, int rc) { wontreturn void ShowUsage(FILE *f, int rc) {
@ -229,6 +236,7 @@ void SendExitMessage(int rc) {
msg[0 + 3] = (RUNITD_MAGIC & 0x000000ff) >> 000; msg[0 + 3] = (RUNITD_MAGIC & 0x000000ff) >> 000;
msg[4] = kRunitExit; msg[4] = kRunitExit;
msg[5] = rc; msg[5] = rc;
INFOF("mbedtls_ssl_write");
CHECK_EQ(sizeof(msg), mbedtls_ssl_write(&ezssl, msg, sizeof(msg))); CHECK_EQ(sizeof(msg), mbedtls_ssl_write(&ezssl, msg, sizeof(msg)));
CHECK_EQ(0, EzTlsFlush(&ezbio, 0, 0)); CHECK_EQ(0, EzTlsFlush(&ezbio, 0, 0));
} }
@ -247,6 +255,7 @@ void SendOutputFragmentMessage(enum RunitCommand kind, unsigned char *buf,
msg[5 + 1] = (size & 0x00ff0000) >> 020; msg[5 + 1] = (size & 0x00ff0000) >> 020;
msg[5 + 2] = (size & 0x0000ff00) >> 010; msg[5 + 2] = (size & 0x0000ff00) >> 010;
msg[5 + 3] = (size & 0x000000ff) >> 000; msg[5 + 3] = (size & 0x000000ff) >> 000;
INFOF("mbedtls_ssl_write");
CHECK_EQ(sizeof(msg), mbedtls_ssl_write(&ezssl, msg, sizeof(msg))); CHECK_EQ(sizeof(msg), mbedtls_ssl_write(&ezssl, msg, sizeof(msg)));
while (size) { while (size) {
CHECK_NE(-1, (rc = mbedtls_ssl_write(&ezssl, buf, size))); CHECK_NE(-1, (rc = mbedtls_ssl_write(&ezssl, buf, size)));
@ -294,6 +303,7 @@ void Recv(void *output, size_t outputsize) {
// get another fixed-size data packet from network // get another fixed-size data packet from network
// pass along error conditions to caller // pass along error conditions to caller
// pass along eof condition to zlib // pass along eof condition to zlib
INFOF("mbedtls_ssl_read");
received = mbedtls_ssl_read(&ezssl, buf, sizeof(buf)); received = mbedtls_ssl_read(&ezssl, buf, sizeof(buf));
if (received < 0) TlsDie("read failed", received); if (received < 0) TlsDie("read failed", received);
// decompress packet completely // decompress packet completely
@ -347,12 +357,14 @@ void HandleClient(void) {
/* read request to run program */ /* read request to run program */
addrsize = sizeof(addr); addrsize = sizeof(addr);
INFOF("accept");
CHECK_NE(-1, (g_clifd = accept4(g_servfd, &addr, &addrsize, SOCK_CLOEXEC))); CHECK_NE(-1, (g_clifd = accept4(g_servfd, &addr, &addrsize, SOCK_CLOEXEC)));
if (fork()) { if (fork()) {
close(g_clifd); close(g_clifd);
return; return;
} }
EzFd(g_clifd); EzFd(g_clifd);
INFOF("EzHandshake");
EzHandshake(); EzHandshake();
addrstr = gc(DescribeAddress(&addr)); addrstr = gc(DescribeAddress(&addr));
DEBUGF("%s %s %s", gc(DescribeAddress(&g_servaddr)), "accepted", addrstr); DEBUGF("%s %s %s", gc(DescribeAddress(&g_servaddr)), "accepted", addrstr);
@ -376,6 +388,7 @@ void HandleClient(void) {
} }
CHECK_NE(-1, (g_exefd = creat(g_exepath, 0700))); CHECK_NE(-1, (g_exefd = creat(g_exepath, 0700)));
LOGIFNEG1(ftruncate(g_exefd, filesize)); LOGIFNEG1(ftruncate(g_exefd, filesize));
INFOF("xwrite");
CHECK_NE(-1, xwrite(g_exefd, exe, filesize)); CHECK_NE(-1, xwrite(g_exefd, exe, filesize));
LOGIFNEG1(close(g_exefd)); LOGIFNEG1(close(g_exefd));
@ -425,6 +438,7 @@ void HandleClient(void) {
fds[0].events = POLLIN; fds[0].events = POLLIN;
fds[1].fd = pipefds[0]; fds[1].fd = pipefds[0];
fds[1].events = POLLIN; fds[1].events = POLLIN;
INFOF("poll");
events = poll(fds, ARRAYLEN(fds), (deadline - now) * 1000); events = poll(fds, ARRAYLEN(fds), (deadline - now) * 1000);
CHECK_NE(-1, events); // EINTR shouldn't be possible CHECK_NE(-1, events); // EINTR shouldn't be possible
if (fds[0].revents) { if (fds[0].revents) {
@ -440,6 +454,7 @@ void HandleClient(void) {
LOGIFNEG1(unlink(g_exepath)); LOGIFNEG1(unlink(g_exepath));
_exit(1); _exit(1);
} }
INFOF("read");
got = read(pipefds[0], g_buf, sizeof(g_buf)); got = read(pipefds[0], g_buf, sizeof(g_buf));
CHECK_NE(-1, got); // EINTR shouldn't be possible CHECK_NE(-1, got); // EINTR shouldn't be possible
if (!got) { if (!got) {
@ -449,6 +464,7 @@ void HandleClient(void) {
fwrite(g_buf, got, 1, stderr); fwrite(g_buf, got, 1, stderr);
SendOutputFragmentMessage(kRunitStderr, g_buf, got); SendOutputFragmentMessage(kRunitStderr, g_buf, got);
} }
INFOF("waitpid");
CHECK_NE(-1, waitpid(child, &wstatus, 0)); // EINTR shouldn't be possible CHECK_NE(-1, waitpid(child, &wstatus, 0)); // EINTR shouldn't be possible
if (WIFEXITED(wstatus)) { if (WIFEXITED(wstatus)) {
if (WEXITSTATUS(wstatus)) { if (WEXITSTATUS(wstatus)) {
@ -463,6 +479,7 @@ void HandleClient(void) {
} }
LOGIFNEG1(unlink(g_exepath)); LOGIFNEG1(unlink(g_exepath));
SendExitMessage(exitcode); SendExitMessage(exitcode);
INFOF("mbedtls_ssl_close_notify");
mbedtls_ssl_close_notify(&ezssl); mbedtls_ssl_close_notify(&ezssl);
LOGIFNEG1(close(g_clifd)); LOGIFNEG1(close(g_clifd));
_exit(0); _exit(0);
@ -524,7 +541,7 @@ void Daemonize(void) {
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
int i; int i;
SetupPresharedKeySsl(MBEDTLS_SSL_IS_SERVER, GetRunitPsk()); SetupPresharedKeySsl(MBEDTLS_SSL_IS_SERVER, GetRunitPsk());
/* __log_level = kLogDebug; */ __log_level = kLogInfo;
GetOpts(argc, argv); GetOpts(argc, argv);
for (i = 3; i < 16; ++i) close(i); for (i = 3; i < 16; ++i) close(i);
errno = 0; errno = 0;

View file

@ -108,6 +108,7 @@
"protip" "protip"
"nxbitsafe" "nxbitsafe"
"vforksafe" "vforksafe"
"threadsafe"
"preinitsafe" "preinitsafe"
"asyncsignalsafe" "asyncsignalsafe"
"notasyncsignalsafe" "notasyncsignalsafe"

View file

@ -146,6 +146,7 @@
#include "third_party/mbedtls/iana.h" #include "third_party/mbedtls/iana.h"
#include "third_party/mbedtls/md.h" #include "third_party/mbedtls/md.h"
#include "third_party/mbedtls/md5.h" #include "third_party/mbedtls/md5.h"
#include "third_party/mbedtls/net_sockets.h"
#include "third_party/mbedtls/oid.h" #include "third_party/mbedtls/oid.h"
#include "third_party/mbedtls/pk.h" #include "third_party/mbedtls/pk.h"
#include "third_party/mbedtls/rsa.h" #include "third_party/mbedtls/rsa.h"

View file

@ -52,6 +52,7 @@
#include "third_party/mbedtls/ctr_drbg.h" #include "third_party/mbedtls/ctr_drbg.h"
#include "third_party/mbedtls/debug.h" #include "third_party/mbedtls/debug.h"
#include "third_party/mbedtls/error.h" #include "third_party/mbedtls/error.h"
#include "third_party/mbedtls/net_sockets.h"
#include "third_party/mbedtls/ssl.h" #include "third_party/mbedtls/ssl.h"
#define OPTS "BIqksvzX:H:C:m:" #define OPTS "BIqksvzX:H:C:m:"