From 03875beadb15f3d1e462b0ffcc2c019ab23947c1 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Thu, 5 Sep 2024 03:17:19 -0700 Subject: [PATCH] Add missing ICANON features --- examples/ctrlc.c | 139 ++++++++---- libc/calls/internal.h | 4 +- libc/calls/linkat.c | 3 +- libc/calls/mkdirat.c | 2 +- libc/calls/park.c | 9 +- libc/calls/poll-nt.c | 55 +++-- libc/calls/read-nt.c | 319 ++++++++++++++++++++++------ libc/calls/readlinkat.c | 1 + libc/calls/readwrite-nt.c | 3 +- libc/calls/renameat.c | 3 +- libc/calls/tcgetattr-nt.c | 36 ++-- libc/calls/tcsetattr-nt.c | 42 ++-- libc/calls/timespec_mono.c | 2 +- libc/calls/unlinkat.c | 3 +- libc/intrin/nomultics.h | 18 +- libc/proc/fork.c | 4 +- libc/proc/wait4-nt.c | 9 +- libc/runtime/zipos-notat.c | 7 +- libc/testlib/benchmark.h | 4 +- tool/build/runit.c | 16 +- tool/build/runitd.c | 60 +++--- tool/viz/clock_nanosleep_accuracy.c | 38 ++-- 22 files changed, 526 insertions(+), 251 deletions(-) diff --git a/examples/ctrlc.c b/examples/ctrlc.c index ee1ca37fa2f..f15f8dae49c 100644 --- a/examples/ctrlc.c +++ b/examples/ctrlc.c @@ -7,20 +7,43 @@ │ • http://creativecommons.org/publicdomain/zero/1.0/ │ ╚─────────────────────────────────────────────────────────────────*/ #endif -#include "libc/assert.h" -#include "libc/calls/calls.h" -#include "libc/calls/struct/sigaction.h" -#include "libc/errno.h" -#include "libc/limits.h" -#include "libc/runtime/runtime.h" -#include "libc/sock/struct/pollfd.h" -#include "libc/stdio/stdio.h" -#include "libc/str/str.h" -#include "libc/sysv/consts/f.h" -#include "libc/sysv/consts/limits.h" -#include "libc/sysv/consts/o.h" -#include "libc/sysv/consts/poll.h" -#include "libc/sysv/consts/sig.h" +#include +#include +#include +#include +#include +#include +#include +#include + +// this program is used by jart for manually testing teletype interrupts +// and canonical mode line editing. this file documents the hidden depth +// of 1960's era computer usage, that's entrenched in primitive i/o apis +// +// manual testing checklist: +// +// - "hello" enter echos "got: hello^J" +// +// - "hello" ctrl-d echos "got: hello" +// +// - "hello" ctrl-r echos "^R\nhello" +// +// - "hello" ctrl-u enter echos "got: ^J" +// +// - ctrl-d during i/o task prints "got eof" and exits +// +// - ctrl-d during cpu task gets delayed until read() is called +// +// - ctrl-c during cpu task echos ^C, then calls SignalHandler() +// asynchronously, and program exits +// +// - ctrl-c during i/o task echos ^C, then calls SignalHandler() +// asynchronously, read() raises EINTR, and program exits +// +// - ctrl-v ctrl-c should echo "^\b" then echo "^C" and insert "\3" +// +// - ctrl-v ctrl-d should echo "^\b" then echo "^D" and insert "\4" +// volatile bool gotsig; @@ -34,23 +57,41 @@ void SignalHandler(int sig) { gotsig = true; } +// this is the easiest way to write a string literal to standard output, +// without formatting. printf() has an enormous binary footprint so it's +// nice to avoid linking that when it is not needed. +#define WRITE(sliteral) write(1, sliteral, sizeof(sliteral) - 1) + int main(int argc, char *argv[]) { - printf("echoing stdin until ctrl+c is pressed\n"); + WRITE("echoing stdin until ctrl+c is pressed\n"); - // you need to set your signal handler using sigaction() rather than - // signal(), since the latter uses .sa_flags=SA_RESTART, which means - // read will restart itself after signals, rather than raising EINTR + // when you type ctrl-c, by default it'll kill the process, unless you + // define a SIGINT handler. there's multiple ways to do it. the common + // way is to say signal(SIGINT, func) which is normally defined to put + // the signal handler in Berkeley-style SA_RESTART mode. that means if + // a signal handler is called while inside a function like read() then + // the read operation will keep going afterwards like nothing happened + // which can make it difficult to break your event loop. to avoid this + // we can use sigaction() without specifying SA_RESTART in sa_flag and + // that'll put the signal in system v mode. this means that whenever a + // signal handler function in your program is called during an i/o op, + // that i/o op will return an EINTR error, so you can churn your loop. + // don't take that error too seriously though since SIGINT can also be + // delivered asynchronously, during the times you're crunching numbers + // rather than performing i/o which means you get no EINTR to warn you sigaction(SIGINT, &(struct sigaction){.sa_handler = SignalHandler}, 0); for (;;) { - // some programs are blocked on cpu rather than i/o - // such programs shall rely on asynchronous signals - printf("doing cpu task...\n"); + // asynchronous signals are needed to interrupt math, which we shall + // simulate here. signals can happen any time any place. that's only + // not the case when you use sigprocmask() to block signals which is + // useful for kicking the can down the road. + WRITE("doing cpu task...\n"); for (volatile int i = 0; i < INT_MAX / 5; ++i) { if (gotsig) { - printf("\rgot ctrl+c asynchronously\n"); + WRITE("\rgot ctrl+c asynchronously\n"); exit(0); } } @@ -71,14 +112,18 @@ int main(int argc, char *argv[]) { // read data from standard input // - // since this is a blocking operation and we're not performing a - // cpu-bound operation it is almost with absolute certainty that - // when the ctrl-c signal gets delivered, it'll happen in read() - // - // it's possible to be more precise if we were building library - // code. for example, you can block signals using sigprocmask() - // and then use pselect() to do the waiting. - printf("doing read i/o task...\n"); + // assuming you started this program in your terminal standard input + // will be plugged into your termios driver, which cosmpolitan codes + // in libc/calls/read-nt.c on windows. your read() function includes + // a primitive version of readline/linenoise called "canonical mode" + // which lets you edit the data that'll be returned by read() before + // it's actually returned. for example, if you type hello and enter, + // then "hello\n" will be returned. if you type hello and then ^D or + // ctrl-d, then "hello" will be returned. the ctrl-d keystroke is in + // fact an ascii control code whose special behavior can be bypassed + // if you type ctrl-v ctrl-d and then enter, in which case "\3\n" is + // returned, also known as ^D^J. + WRITE("doing read i/o task...\n"); int got = read(0, buf, sizeof(buf)); // check if the read operation failed @@ -94,10 +139,10 @@ int main(int argc, char *argv[]) { // the \r character is needed so when the line is printed // it'll overwrite the ^C that got echo'd with the ctrl-c if (gotsig) { - printf("\rgot ctrl+c via i/o eintr\n"); + WRITE("\rgot ctrl+c via i/o eintr\n"); exit(0); } else { - printf("\rgot spurious eintr\n"); + WRITE("\rgot spurious eintr\n"); continue; } } else { @@ -109,16 +154,34 @@ int main(int argc, char *argv[]) { // check if the user typed ctrl-d which closes the input handle if (!got) { - printf("got eof\n"); + WRITE("got eof\n"); exit(0); } - // relay read data to standard output + // visualize line data returned by canonical mode to standard output + // + // it's usually safe to ignore the return code of write; your system + // will send SIGPIPE if there's any problem, which kills by default. // - // it's usually safe to ignore the return code of write. the - // operating system will send SIGPIPE if there's any problem - // which kills the process by default + // it's possible to use keyboard shortcuts to embed control codes in + // the line. so we visualize them using the classic tty notation. it + // is also possible to type the ascii representation, so we use bold + // to visually distinguish ascii codes. see also o//examples/ttyinfo write(1, "got: ", 5); - write(1, buf, got); + for (int i = 0; i < got; ++i) { + if (isascii(buf[i])) { + if (iscntrl(buf[i])) { + char ctl[2]; + ctl[0] = '^'; + ctl[1] = buf[i] ^ 0100; + WRITE("\033[1m"); + write(1, ctl, 2); + WRITE("\033[0m"); + } else { + write(1, &buf[i], 1); + } + } + } + WRITE("\n"); } } diff --git a/libc/calls/internal.h b/libc/calls/internal.h index 04e87a36cd2..9dfe2b552e4 100644 --- a/libc/calls/internal.h +++ b/libc/calls/internal.h @@ -1,9 +1,10 @@ #ifndef COSMOPOLITAN_LIBC_CALLS_INTERNAL_H_ #define COSMOPOLITAN_LIBC_CALLS_INTERNAL_H_ #include "libc/atomic.h" -#include "libc/intrin/fds.h" +#include "libc/calls/struct/sigset.h" #include "libc/calls/struct/sigval.h" #include "libc/dce.h" +#include "libc/intrin/fds.h" #include "libc/macros.h" #include "libc/stdbool.h" @@ -25,6 +26,7 @@ uint32_t sys_getuid_nt(void); int __ensurefds_unlocked(int); void __printfds(struct Fd *, size_t); int CountConsoleInputBytes(void); +int CountConsoleInputBytesBlocking(uint32_t, sigset_t); int FlushConsoleInputBytes(void); int64_t GetConsoleInputHandle(void); int64_t GetConsoleOutputHandle(void); diff --git a/libc/calls/linkat.c b/libc/calls/linkat.c index 09c2153b780..84237baf3de 100644 --- a/libc/calls/linkat.c +++ b/libc/calls/linkat.c @@ -34,6 +34,7 @@ * * @param flags can have AT_EMPTY_PATH or AT_SYMLINK_NOFOLLOW * @return 0 on success, or -1 w/ errno + * @raise EROFS if either path is under /zip/... * @asyncsignalsafe */ int linkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, @@ -42,7 +43,7 @@ int linkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, if (_weaken(__zipos_notat) && ((rc = __zipos_notat(olddirfd, oldpath)) == -1 || (rc = __zipos_notat(newdirfd, newpath)) == -1)) { - STRACE("zipos fchownat not supported yet"); + rc = erofs(); } else if (!IsWindows()) { rc = sys_linkat(olddirfd, oldpath, newdirfd, newpath, flags); } else { diff --git a/libc/calls/mkdirat.c b/libc/calls/mkdirat.c index 64ad9ea2d20..b4a2cb1b42a 100644 --- a/libc/calls/mkdirat.c +++ b/libc/calls/mkdirat.c @@ -53,7 +53,7 @@ int mkdirat(int dirfd, const char *path, unsigned mode) { int rc; if (_weaken(__zipos_notat) && (rc = __zipos_notat(dirfd, path)) == -1) { - STRACE("zipos mkdirat not supported yet"); + rc = erofs(); } else if (!IsWindows()) { rc = sys_mkdirat(dirfd, path, mode); } else { diff --git a/libc/calls/park.c b/libc/calls/park.c index 286c775559f..eb8f730547f 100644 --- a/libc/calls/park.c +++ b/libc/calls/park.c @@ -34,9 +34,8 @@ static textwindows int _park_thread(uint32_t msdelay, sigset_t waitmask, int sig, handler_was_called; if (_check_cancel() == -1) return -1; - if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) { + if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) goto HandleSignal; - } int expect = 0; atomic_int futex = 0; struct PosixThread *pt = _pthread_self(); @@ -49,9 +48,11 @@ static textwindows int _park_thread(uint32_t msdelay, sigset_t waitmask, handler_was_called = _weaken(__sig_relay)(sig, SI_KERNEL, waitmask); if (_check_cancel() == -1) return -1; - if (!restartable || (handler_was_called & SIG_HANDLED_NO_RESTART)) { + if (handler_was_called & SIG_HANDLED_NO_RESTART) return eintr(); - } + if (handler_was_called & SIG_HANDLED_SA_RESTART) + if (!restartable) + return eintr(); } return 0; } diff --git a/libc/calls/poll-nt.c b/libc/calls/poll-nt.c index fab3a86489b..f20be2baec3 100644 --- a/libc/calls/poll-nt.c +++ b/libc/calls/poll-nt.c @@ -79,15 +79,15 @@ static textwindows int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds, bool ok; uint64_t millis; uint32_t cm, avail, waitfor; - struct sys_pollfd_nt pipefds[8]; + struct sys_pollfd_nt pipefds[64]; struct sys_pollfd_nt sockfds[64]; int pipeindices[ARRAYLEN(pipefds)]; int sockindices[ARRAYLEN(sockfds)]; - struct timespec started, deadline, remain, now; + struct timespec deadline, remain, now; int i, rc, sn, pn, gotinvals, gotpipes, gotsocks; - started = timespec_real(); - deadline = timespec_add(started, timespec_frommillis(ms ? *ms : -1u)); + waitfor = ms ? *ms : -1u; + deadline = timespec_add(timespec_mono(), timespec_frommillis(waitfor)); // do the planning // we need to read static variables @@ -168,16 +168,39 @@ static textwindows int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds, pipefds[i].revents |= POLLERR_; } } else if (GetConsoleMode(pipefds[i].handle, &cm)) { - switch (CountConsoleInputBytes()) { - case 0: - break; - case -1: - pipefds[i].revents &= ~POLLWRNORM_; - pipefds[i].revents |= POLLHUP_; - break; - default: - pipefds[i].revents |= POLLRDNORM_; - break; + // some programs like bash like to poll([stdin], 1, -1) so let's + // avoid busy looping in such cases. we could generalize this to + // always avoid busy loops, but we'd need poll to launch threads + if (pn == 1 && sn == 0 && (pipefds[i].events & POLLRDNORM_)) { + int err = errno; + switch (CountConsoleInputBytesBlocking(waitfor, sigmask)) { + case -1: + if (errno == EINTR || errno == ECANCELED) + return -1; + errno = err; + pipefds[i].revents &= ~POLLWRNORM_; + pipefds[i].revents |= POLLERR_; + break; + case 0: + pipefds[i].revents &= ~POLLWRNORM_; + pipefds[i].revents |= POLLHUP_; + break; + default: + pipefds[i].revents |= POLLRDNORM_; + break; + } + } else { + switch (CountConsoleInputBytes()) { + case 0: + break; + case -1: + pipefds[i].revents &= ~POLLWRNORM_; + pipefds[i].revents |= POLLHUP_; + break; + default: + pipefds[i].revents |= POLLRDNORM_; + break; + } } } else { // we have no way of polling if a non-socket is readable yet @@ -202,7 +225,7 @@ static textwindows int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds, // check for pending signals, thread cancelation, etc. waitfor = 0; if (!gotinvals && !gotsocks && !gotpipes) { - now = timespec_real(); + now = timespec_mono(); if (timespec_cmp(now, deadline) < 0) { remain = timespec_sub(deadline, now); millis = timespec_tomillis(remain); @@ -211,7 +234,7 @@ static textwindows int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds, if (waitfor) { POLLTRACE("poll() sleeping for %'d out of %'lu ms", waitfor, timespec_tomillis(remain)); - if ((rc = _park_norestart(waitfor, sigmask)) == -1) + if (_park_norestart(waitfor, sigmask) == -1) return -1; // eintr, ecanceled, etc. } } diff --git a/libc/calls/read-nt.c b/libc/calls/read-nt.c index 874f603476a..69e7d17e8f9 100644 --- a/libc/calls/read-nt.c +++ b/libc/calls/read-nt.c @@ -23,6 +23,7 @@ #include "libc/calls/state.internal.h" #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/sigset.internal.h" +#include "libc/calls/struct/timespec.h" #include "libc/calls/syscall_support-nt.internal.h" #include "libc/cosmo.h" #include "libc/ctype.h" @@ -31,7 +32,9 @@ #include "libc/intrin/describeflags.h" #include "libc/intrin/dll.h" #include "libc/intrin/fds.h" +#include "libc/intrin/kprintf.h" #include "libc/intrin/nomultics.h" +#include "libc/intrin/safemacros.h" #include "libc/intrin/strace.h" #include "libc/intrin/weaken.h" #include "libc/macros.h" @@ -132,6 +135,7 @@ struct Keystrokes { atomic_uint once; bool end_of_file; bool ohno_decckm; + bool bypass_mode; uint16_t utf16hs; int16_t freekeys; int64_t cin, cot; @@ -149,12 +153,12 @@ textwindows void WipeKeystrokes(void) { bzero(&__keystroke, sizeof(__keystroke)); } -static textwindows void FreeKeystrokeImpl(struct Dll *key) { +textwindows static void FreeKeystrokeImpl(struct Dll *key) { dll_make_first(&__keystroke.free, key); ++__keystroke.freekeys; } -static textwindows struct Keystroke *NewKeystroke(void) { +textwindows static struct Keystroke *NewKeystroke(void) { struct Dll *e = dll_first(__keystroke.free); if (!e) // See MIN(freekeys) before ReadConsoleInput() __builtin_trap(); @@ -165,18 +169,18 @@ static textwindows struct Keystroke *NewKeystroke(void) { return k; } -static textwindows void FreeKeystroke(struct Dll **list, struct Dll *key) { +textwindows static void FreeKeystroke(struct Dll **list, struct Dll *key) { dll_remove(list, key); FreeKeystrokeImpl(key); } -static textwindows void FreeKeystrokes(struct Dll **list) { +textwindows static void FreeKeystrokes(struct Dll **list) { struct Dll *key; while ((key = dll_first(*list))) FreeKeystroke(list, key); } -static textwindows void OpenConsole(void) { +textwindows static void OpenConsole(void) { __keystroke.vkt = kVirtualKey; __keystroke.cin = CreateFile(u"CONIN$", kNtGenericRead | kNtGenericWrite, kNtFileShareRead, 0, kNtOpenExisting, 0, 0); @@ -188,21 +192,21 @@ static textwindows void OpenConsole(void) { } } -static textwindows int AddSignal(int sig) { +textwindows static int AddSignal(int sig) { atomic_fetch_or_explicit(&__get_tls()->tib_sigpending, 1ull << (sig - 1), memory_order_relaxed); return 0; } -static textwindows void InitConsole(void) { +textwindows static void InitConsole(void) { cosmo_once(&__keystroke.once, OpenConsole); } -static textwindows void LockKeystrokes(void) { +textwindows static void LockKeystrokes(void) { pthread_mutex_lock(&__keystroke.lock); } -static textwindows void UnlockKeystrokes(void) { +textwindows static void UnlockKeystrokes(void) { pthread_mutex_unlock(&__keystroke.lock); } @@ -216,14 +220,14 @@ textwindows int64_t GetConsoleOutputHandle(void) { return __keystroke.cot; } -static textwindows bool IsMouseModeCommand(int x) { +textwindows static bool IsMouseModeCommand(int x) { return x == 1000 || // SET_VT200_MOUSE x == 1002 || // SET_BTN_EVENT_MOUSE x == 1006 || // SET_SGR_EXT_MODE_MOUSE x == 1015; // SET_URXVT_EXT_MODE_MOUSE } -static textwindows int GetVirtualKey(uint16_t vk, bool shift, bool ctrl) { +textwindows static int GetVirtualKey(uint16_t vk, bool shift, bool ctrl) { for (int i = 0; __keystroke.vkt[i].vk; ++i) { if (__keystroke.vkt[i].vk == vk) { if (shift && ctrl) { @@ -240,7 +244,7 @@ static textwindows int GetVirtualKey(uint16_t vk, bool shift, bool ctrl) { return 0; } -static textwindows int ProcessKeyEvent(const struct NtInputRecord *r, char *p) { +textwindows static int ProcessKeyEvent(const struct NtInputRecord *r, char *p) { uint32_t c = r->Event.KeyEvent.uChar.UnicodeChar; uint16_t vk = r->Event.KeyEvent.wVirtualKeyCode; @@ -327,7 +331,7 @@ static textwindows int ProcessKeyEvent(const struct NtInputRecord *r, char *p) { // handle ctrl-\ and ctrl-c // note we define _POSIX_VDISABLE as zero // tcsetattr() lets anyone reconfigure these keybindings - if (c && !(__ttyconf.magic & kTtyNoIsigs)) { + if (c && !(__ttyconf.magic & kTtyNoIsigs) && !__keystroke.bypass_mode) { if (c == __ttyconf.vintr) { return AddSignal(SIGINT); } else if (c == __ttyconf.vquit) { @@ -337,7 +341,9 @@ static textwindows int ProcessKeyEvent(const struct NtInputRecord *r, char *p) { // handle ctrl-d which generates end-of-file, unless pending line data // is present, in which case we flush that without the newline instead - if (c && c == __ttyconf.veof && !(__ttyconf.magic & kTtyUncanon)) { + if (c && c == __ttyconf.veof && // + !__keystroke.bypass_mode && // + !(__ttyconf.magic & kTtyUncanon)) { if (dll_is_empty(__keystroke.line)) { __keystroke.end_of_file = true; } else { @@ -367,7 +373,7 @@ static textwindows int ProcessKeyEvent(const struct NtInputRecord *r, char *p) { // - write(1, "\e[?1000;1002;1015;1006h") to enable // - write(1, "\e[?1000;1002;1015;1006l") to disable // See o//examples/ttyinfo and o//tool/viz/life -static textwindows int ProcessMouseEvent(const struct NtInputRecord *r, +textwindows static int ProcessMouseEvent(const struct NtInputRecord *r, char *b) { char *p = b; unsigned char e = 0; @@ -424,7 +430,7 @@ static textwindows int ProcessMouseEvent(const struct NtInputRecord *r, return p - b; } -static textwindows int ConvertConsoleInputToAnsi(const struct NtInputRecord *r, +textwindows static int ConvertConsoleInputToAnsi(const struct NtInputRecord *r, char p[hasatleast 23]) { switch (r->EventType) { case kNtKeyEvent: @@ -438,18 +444,19 @@ static textwindows int ConvertConsoleInputToAnsi(const struct NtInputRecord *r, } } -static textwindows void WriteTty(const char *p, size_t n) { +textwindows static void WriteTty(const char *p, size_t n) { WriteFile(__keystroke.cot, p, n, 0, 0); } -static textwindows bool IsCtl(int c) { - return isascii(c) && iscntrl(c) && c != '\n' && c != '\t'; +textwindows static bool IsCtl(int c, bool escape_harder) { + return isascii(c) && iscntrl(c) && + (escape_harder || (c != '\n' && c != '\t')); } -static textwindows void WriteCtl(const char *p, size_t n) { +textwindows static void WriteCtl(const char *p, size_t n, bool escape_harder) { size_t i; for (i = 0; i < n; ++i) { - if (IsCtl(p[i])) { + if (IsCtl(p[i], escape_harder)) { char ctl[2]; ctl[0] = '^'; ctl[1] = p[i] ^ 0100; @@ -460,19 +467,22 @@ static textwindows void WriteCtl(const char *p, size_t n) { } } -static textwindows void EchoTty(const char *p, size_t n) { - if (__ttyconf.magic & kTtyEchoRaw) { - WriteTty(p, n); - } else { - WriteCtl(p, n); +textwindows static void EchoTty(const char *p, size_t n, bool escape_harder) { + if (!(__ttyconf.magic & kTtySilence)) { + if (__ttyconf.magic & kTtyEchoRaw) { + WriteTty(p, n); + } else { + WriteCtl(p, n, escape_harder); + } } } -static textwindows void EraseCharacter(void) { - WriteTty("\b \b", 3); +textwindows static void EraseCharacter(bool should_echo) { + if (should_echo) + WriteTty("\b \b", 3); } -static textwindows bool EraseKeystroke(void) { +textwindows static bool EraseKeystroke(bool should_echo) { struct Dll *e; if ((e = dll_last(__keystroke.line))) { struct Keystroke *k = KEYSTROKE_CONTAINER(e); @@ -480,9 +490,9 @@ static textwindows bool EraseKeystroke(void) { for (int i = k->buflen; i--;) { if ((k->buf[i] & 0300) == 0200) continue; // utf-8 cont - EraseCharacter(); - if (!(__ttyconf.magic & kTtyEchoRaw) && IsCtl(k->buf[i])) - EraseCharacter(); + EraseCharacter(should_echo); + if (!(__ttyconf.magic & kTtyEchoRaw) && IsCtl(k->buf[i], true)) + EraseCharacter(should_echo); } return true; } else { @@ -490,7 +500,17 @@ static textwindows bool EraseKeystroke(void) { } } -static textwindows void IngestConsoleInputRecord(struct NtInputRecord *r) { +textwindows static int IsLookingAtSpace(void) { + struct Dll *e; + if ((e = dll_last(__keystroke.line))) { + struct Keystroke *k = KEYSTROKE_CONTAINER(e); + return k->buflen == 1 && isascii(k->buf[0]) && isspace(k->buf[0]); + } else { + return -1; + } +} + +textwindows static void IngestConsoleInputRecord(struct NtInputRecord *r) { // convert win32 console event into ansi int len; @@ -498,19 +518,103 @@ static textwindows void IngestConsoleInputRecord(struct NtInputRecord *r) { if (!(len = ConvertConsoleInputToAnsi(r, buf))) return; + // handle ctrl-v in canonical mode + // the next keystroke will bypass input processing + if (!(__ttyconf.magic & kTtyUncanon) && // ICANON + !(__ttyconf.magic & kTtyNoIexten)) { // IEXTEN + if (__keystroke.bypass_mode) { + struct Keystroke *k = NewKeystroke(); + memcpy(k->buf, buf, sizeof(k->buf)); + k->buflen = len; + dll_make_last(&__keystroke.line, &k->elem); + EchoTty(buf, len, true); + if (!__keystroke.freekeys) { + dll_make_last(&__keystroke.list, __keystroke.line); + __keystroke.line = 0; + } + __keystroke.bypass_mode = false; + return; + } else if (len == 1 && buf[0] && // + (buf[0] & 255) == __ttyconf.vlnext) { + __keystroke.bypass_mode = true; + if (!(__ttyconf.magic & kTtySilence) && // ECHO + !(__ttyconf.magic & kTtyEchoRaw)) // ECHOCTL + WriteTty("^\b", 2); + return; + } + } + // handle backspace in canonical mode if (len == 1 && buf[0] && // (buf[0] & 255) == __ttyconf.verase && // - !(__ttyconf.magic & kTtyUncanon)) { - EraseKeystroke(); + !(__ttyconf.magic & kTtyUncanon) && // + !(__ttyconf.magic & kTtyNoIexten)) { + bool should_visually_erase = // + !(__ttyconf.magic & kTtySilence) && // ECHO + !(__ttyconf.magic & kTtyNoEchoe); // ECHOE + EraseKeystroke(should_visually_erase); + if (!(__ttyconf.magic & kTtySilence) && // ECHO + (__ttyconf.magic & kTtyNoEchoe) && // !ECHOE + !(__ttyconf.magic & kTtyEchoRaw)) // ECHOCTL + WriteCtl(buf, len, true); + return; + } + + // handle ctrl-w in canonical mode + // this lets you erase the last word + if (len == 1 && buf[0] && // + (buf[0] & 255) == __ttyconf.vwerase && // + !(__ttyconf.magic & kTtyUncanon) && // + !(__ttyconf.magic & kTtyNoIexten)) { + bool should_visually_erase = // + !(__ttyconf.magic & kTtySilence) && // ECHO + !(__ttyconf.magic & kTtyNoEchoe); // ECHOE + while (IsLookingAtSpace() == 1) + EraseKeystroke(should_visually_erase); + while (IsLookingAtSpace() == 0) + EraseKeystroke(should_visually_erase); + if (!(__ttyconf.magic & kTtySilence) && // ECHO + (__ttyconf.magic & kTtyNoEchoe) && // !ECHOE + !(__ttyconf.magic & kTtyEchoRaw)) // ECHOCTL + WriteCtl(buf, len, true); return; } // handle kill in canonical mode + // this clears the line you're editing if (len == 1 && buf[0] && // (buf[0] & 255) == __ttyconf.vkill && // - !(__ttyconf.magic & kTtyUncanon)) { - while (EraseKeystroke()) { + !(__ttyconf.magic & kTtyUncanon) && // + !(__ttyconf.magic & kTtyNoIexten)) { + bool should_visually_kill = // + !(__ttyconf.magic & kTtySilence) && // ECHO + !(__ttyconf.magic & kTtyNoEchok) && // ECHOK + !(__ttyconf.magic & kTtyNoEchoke); // ECHOKE + while (EraseKeystroke(should_visually_kill)) { + } + if (!(__ttyconf.magic & kTtySilence) && // ECHO + !(__ttyconf.magic & kTtyNoEchok) && // ECHOK + (__ttyconf.magic & kTtyNoEchoke) && // !ECHOKE + !(__ttyconf.magic & kTtyEchoRaw)) // ECHOCTL + WriteCtl(buf, len, true); + return; + } + + // handle ctrl-r in canonical mode + // this reprints the line you're editing + if (len == 1 && buf[0] && // + (buf[0] & 255) == __ttyconf.vreprint && // + !(__ttyconf.magic & kTtyUncanon) && // ICANON + !(__ttyconf.magic & kTtyNoIexten) && // IEXTEN + !(__ttyconf.magic & kTtySilence)) { // ECHO + struct Dll *e; + if (!(__ttyconf.magic & kTtyEchoRaw)) + WriteCtl(buf, len, true); + WriteTty("\r\n", 2); + for (e = dll_first(__keystroke.line); e; + e = dll_next(__keystroke.line, e)) { + struct Keystroke *k = KEYSTROKE_CONTAINER(e); + WriteCtl(k->buf, k->buflen, true); } return; } @@ -522,8 +626,7 @@ static textwindows void IngestConsoleInputRecord(struct NtInputRecord *r) { // echo input if it was successfully recorded // assuming the win32 console isn't doing it already - if (!(__ttyconf.magic & kTtySilence)) - EchoTty(buf, len); + EchoTty(buf, len, false); // save keystroke to appropriate list if (__ttyconf.magic & kTtyUncanon) { @@ -535,14 +638,15 @@ static textwindows void IngestConsoleInputRecord(struct NtInputRecord *r) { if (!__keystroke.freekeys || (len == 1 && buf[0] && ((buf[0] & 255) == '\n' || // (buf[0] & 255) == __ttyconf.veol || // - (buf[0] & 255) == __ttyconf.veol2))) { + ((buf[0] & 255) == __ttyconf.veol2 && + !(__ttyconf.magic & kTtyNoIexten))))) { dll_make_last(&__keystroke.list, __keystroke.line); __keystroke.line = 0; } } } -static textwindows void IngestConsoleInput(void) { +textwindows static void IngestConsoleInput(void) { uint32_t i, n; struct NtInputRecord records[16]; for (;;) { @@ -552,7 +656,7 @@ static textwindows void IngestConsoleInput(void) { return; if (!GetNumberOfConsoleInputEvents(__keystroke.cin, &n)) goto UnexpectedEof; - if (!n) + if (!n || !__keystroke.freekeys) return; n = MIN(__keystroke.freekeys, MIN(ARRAYLEN(records), n)); if (!ReadConsoleInput(__keystroke.cin, records, n, &n)) @@ -657,12 +761,11 @@ textwindows void InterceptTerminalCommands(const char *data, size_t size) { __builtin_unreachable(); } } - if (cm2 != cm) { + if (cm2 != cm) SetConsoleMode(GetConsoleInputHandle(), cm2); - } } -static textwindows bool DigestConsoleInput(char *data, size_t size, int *rc) { +textwindows static bool DigestConsoleInput(char *data, size_t size, int *rc) { // handle eof once available input is consumed if (dll_is_empty(__keystroke.list) && __keystroke.end_of_file) { @@ -702,21 +805,42 @@ static textwindows bool DigestConsoleInput(char *data, size_t size, int *rc) { } } -static textwindows int WaitForConsole(struct Fd *f, sigset_t waitmask) { +textwindows static uint32_t DisableProcessedInput(void) { + // the time has come to ensure that ctrl-v ctrl-c works in icanon mode + // we're perfectly capable of generating a SIGINT or SIGQUIT ourselves + // while the cosmo termios driver is in control; so we disable windows + // console input processing for now; we'll turn it back on when we are + // done, since it's useful for ensuring asynchronous signal deliveries + uint32_t inmode = 0; + if (GetConsoleMode(__keystroke.cin, &inmode)) + if (inmode & kNtEnableProcessedInput) + SetConsoleMode(__keystroke.cin, inmode & ~kNtEnableProcessedInput); + return inmode; +} + +textwindows static void RestoreProcessedInput(uint32_t inmode) { + // re-enable win32 console input processing, if it was enabled when we + // started, and no signal handler callbacks changed things in-between. + if (inmode & kNtEnableProcessedInput) { + uint32_t inmode2; + if (GetConsoleMode(__keystroke.cin, &inmode2)) + if (inmode2 == (inmode & ~kNtEnableProcessedInput)) + SetConsoleMode(__keystroke.cin, inmode); + } +} + +textwindows static int CountConsoleInputBytesBlockingImpl(uint32_t ms, + sigset_t waitmask, + bool restartable) { int sig; int64_t sem; - uint32_t wi, ms = -1; - if (!__ttyconf.vmin) { - if (!__ttyconf.vtime) { - return 0; // non-blocking w/o raising eagain - } else { - ms = __ttyconf.vtime * 100; - } - } + uint32_t wi; + struct timespec now, deadline; + InitConsole(); + deadline = timespec_add(timespec_mono(), timespec_frommillis(ms)); +RestartOperation: if (_check_cancel() == -1) return -1; - if (f->flags & _O_NONBLOCK) - return eagain(); if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) goto DeliverSignal; struct PosixThread *pt = _pthread_self(); @@ -726,12 +850,40 @@ static textwindows int WaitForConsole(struct Fd *f, sigset_t waitmask) { wi = WaitForMultipleObjects(2, (int64_t[2]){__keystroke.cin, sem}, 0, ms); atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release); CloseHandle(sem); + + // check for wait timeout if (wi == kNtWaitTimeout) - return 0; // vtime elapsed - if (wi == 0) - return -2; // console data + return etimedout(); + + // handle event on console handle. this means we can now read from the + // conosle without blocking. so the first thing we do is slurp up your + // keystroke data. some of those keystrokes might cause a signal to be + // raised. so we need to check for pending signals again and handle it + if (wi == 0) { + int got = CountConsoleInputBytes(); + if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) + goto DeliverSignal; + if (got == -1) + // this is a bona fide eof and console errors are logged to strace + return 0; + if (got == 0) { + // this can happen for multiple reasons. first our driver controls + // user interactions in canonical mode. secondly we could lose the + // race with another thread that's reading input. + now = timespec_mono(); + if (timespec_cmp(now, deadline) >= 0) + return etimedout(); + ms = min(-1u, timespec_tomillis(timespec_sub(deadline, now))); + goto RestartOperation; + } + return got; + } + + // handle wait itself failing if (wi != 1) - return __winerr(); // wait failed + return __winerr(); + + // handle event on throwaway semaphore, it is poked by signal delivery if (_weaken(__sig_get)) { if (!(sig = _weaken(__sig_get)(waitmask))) return eintr(); @@ -739,24 +891,57 @@ static textwindows int WaitForConsole(struct Fd *f, sigset_t waitmask) { int handler_was_called = _weaken(__sig_relay)(sig, SI_KERNEL, waitmask); if (_check_cancel() == -1) return -1; - if (!(handler_was_called & SIG_HANDLED_NO_RESTART)) - return -2; + if (handler_was_called & SIG_HANDLED_NO_RESTART) + return eintr(); + if (handler_was_called & SIG_HANDLED_SA_RESTART) + if (!restartable) + return eintr(); } - return eintr(); + goto RestartOperation; } -static textwindows ssize_t ReadFromConsole(struct Fd *f, void *data, +textwindows int CountConsoleInputBytesBlocking(uint32_t ms, sigset_t waitmask) { + uint32_t inmode = DisableProcessedInput(); + int rc = CountConsoleInputBytesBlockingImpl(ms, waitmask, false); + RestoreProcessedInput(inmode); + return rc; +} + +textwindows static int WaitToReadFromConsole(struct Fd *f, sigset_t waitmask) { + uint32_t ms = -1; + if (!__ttyconf.vmin) { + if (!__ttyconf.vtime) { + return 0; // non-blocking w/o raising eagain + } else { + ms = __ttyconf.vtime * 100; + } + } + if (f->flags & _O_NONBLOCK) + return eagain(); + int olderr = errno; + int rc = CountConsoleInputBytesBlockingImpl(ms, waitmask, true); + if (rc == -1 && errno == ETIMEDOUT) { + // read() never raises ETIMEDOUT so if vtime elapses we raise an EOF + errno = olderr; + rc = 0; + } + return rc; +} + +textwindows static ssize_t ReadFromConsole(struct Fd *f, void *data, size_t size, sigset_t waitmask) { int rc; InitConsole(); + uint32_t inmode = DisableProcessedInput(); do { LockKeystrokes(); IngestConsoleInput(); bool done = DigestConsoleInput(data, size, &rc); UnlockKeystrokes(); if (done) - return rc; - } while ((rc = WaitForConsole(f, waitmask)) == -2); + break; + } while ((rc = WaitToReadFromConsole(f, waitmask)) > 0); + RestoreProcessedInput(inmode); return rc; } @@ -794,7 +979,7 @@ textwindows ssize_t ReadBuffer(int fd, void *data, size_t size, int64_t offset, } } -static textwindows ssize_t ReadIovecs(int fd, const struct iovec *iov, +textwindows static ssize_t ReadIovecs(int fd, const struct iovec *iov, size_t iovlen, int64_t opt_offset, sigset_t waitmask) { ssize_t rc; diff --git a/libc/calls/readlinkat.c b/libc/calls/readlinkat.c index dc5041c210e..a9b539cfb6e 100644 --- a/libc/calls/readlinkat.c +++ b/libc/calls/readlinkat.c @@ -57,6 +57,7 @@ ssize_t readlinkat(int dirfd, const char *path, char *buf, size_t bufsiz) { } else if (_weaken(__zipos_notat) && (bytes = __zipos_notat(dirfd, path)) == -1) { STRACE("TODO: zipos support for readlinkat"); + bytes = einval(); } else if (!IsWindows()) { bytes = sys_readlinkat(dirfd, path, buf, bufsiz); } else { diff --git a/libc/calls/readwrite-nt.c b/libc/calls/readwrite-nt.c index 6ef3f376c66..30516b4fbda 100644 --- a/libc/calls/readwrite-nt.c +++ b/libc/calls/readwrite-nt.c @@ -86,9 +86,8 @@ sys_readwrite_nt(int fd, void *data, size_t size, ssize_t offset, __cursor_unlock(f->cursor); return -1; // ECANCELED } - if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) { + if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) goto HandleInterrupt; - } // signals have already been fully blocked by caller // perform i/o operation with atomic signal/cancel checking diff --git a/libc/calls/renameat.c b/libc/calls/renameat.c index 80f2a230c0b..821cdba4b99 100644 --- a/libc/calls/renameat.c +++ b/libc/calls/renameat.c @@ -41,6 +41,7 @@ * @param newdirfd is normally AT_FDCWD but if it's an open directory * and newpath is relative, then newpath become relative to dirfd * @return 0 on success, or -1 w/ errno + * @raise EROFS if either path is under /zip/... */ int renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) { @@ -48,7 +49,7 @@ int renameat(int olddirfd, const char *oldpath, int newdirfd, if (_weaken(__zipos_notat) && ((rc = __zipos_notat(olddirfd, oldpath)) == -1 || (rc = __zipos_notat(newdirfd, newpath)) == -1)) { - STRACE("zipos renameat not supported yet"); + rc = erofs(); } else if (!IsWindows()) { rc = sys_renameat(olddirfd, oldpath, newdirfd, newpath); } else { diff --git a/libc/calls/tcgetattr-nt.c b/libc/calls/tcgetattr-nt.c index 00950d3c0f7..8e4c729453e 100644 --- a/libc/calls/tcgetattr-nt.c +++ b/libc/calls/tcgetattr-nt.c @@ -18,9 +18,9 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" #include "libc/calls/internal.h" -#include "libc/intrin/fds.h" #include "libc/calls/struct/termios.h" #include "libc/calls/syscall-nt.internal.h" +#include "libc/intrin/fds.h" #include "libc/intrin/nomultics.h" #include "libc/nt/console.h" #include "libc/nt/enum/consolemodeflags.h" @@ -58,32 +58,34 @@ textwindows int tcgetattr_nt(int fd, struct termios *tio) { // kNtEnableLineInput and kNtEnableEchoInput only apply to programs // that call ReadFile() or ReadConsole(). since we do not use them, // the flags could serve the purpose of inter-process communication - if ((inmode & kNtEnableLineInput) || !(__ttyconf.magic & kTtyUncanon)) { + if ((inmode & kNtEnableLineInput) || !(__ttyconf.magic & kTtyUncanon)) tio->c_lflag |= ICANON; - } + // kNtEnableEchoInput only works with kNtEnableLineInput enabled. - if ((inmode & kNtEnableEchoInput) || !(__ttyconf.magic & kTtySilence)) { + if ((inmode & kNtEnableEchoInput) || !(__ttyconf.magic & kTtySilence)) tio->c_lflag |= ECHO; - } + // The Windows console itself always echos control codes as ASCII. - if ((inmode & kNtEnableEchoInput) || !(__ttyconf.magic & kTtyEchoRaw)) { + if (!(__ttyconf.magic & kTtyEchoRaw)) tio->c_lflag |= ECHOCTL; - } - if (!(__ttyconf.magic & kTtyNoCr2Nl)) { + + if (!(__ttyconf.magic & kTtyNoEchoe)) + tio->c_lflag |= ECHOE; + if (!(__ttyconf.magic & kTtyNoEchok)) + tio->c_lflag |= ECHOK; + if (!(__ttyconf.magic & kTtyNoEchoke)) + tio->c_lflag |= ECHOKE; + + if (!(__ttyconf.magic & kTtyNoCr2Nl)) tio->c_iflag |= ICRNL; - } - if (!(__ttyconf.magic & kTtyNoIsigs)) { + if (!(__ttyconf.magic & kTtyNoIsigs)) tio->c_lflag |= ISIG; - } - if (inmode & kNtEnableProcessedInput) { + if (!(__ttyconf.magic & kTtyNoIexten)) tio->c_lflag |= IEXTEN; - } - if (outmode & kNtEnableProcessedOutput) { + if (outmode & kNtEnableProcessedOutput) tio->c_oflag |= OPOST; - } - if (!(outmode & kNtDisableNewlineAutoReturn)) { + if (!(outmode & kNtDisableNewlineAutoReturn)) tio->c_oflag |= OPOST | ONLCR; - } return 0; } diff --git a/libc/calls/tcsetattr-nt.c b/libc/calls/tcsetattr-nt.c index 4a5484ad3b7..984ff48ec03 100644 --- a/libc/calls/tcsetattr-nt.c +++ b/libc/calls/tcsetattr-nt.c @@ -18,10 +18,10 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" #include "libc/calls/internal.h" -#include "libc/intrin/fds.h" #include "libc/calls/struct/termios.h" #include "libc/calls/syscall-nt.internal.h" #include "libc/calls/ttydefaults.h" +#include "libc/intrin/fds.h" #include "libc/intrin/nomultics.h" #include "libc/nt/console.h" #include "libc/nt/enum/consolemodeflags.h" @@ -61,40 +61,50 @@ textwindows int tcsetattr_nt(int fd, int opt, const struct termios *tio) { inmode &= ~kNtEnableQuickEditMode; __ttyconf.magic |= kTtyUncanon; } - if (!(tio->c_iflag & ICRNL)) { + if (!(tio->c_iflag & ICRNL)) __ttyconf.magic |= kTtyNoCr2Nl; - } - if (!(tio->c_lflag & ECHOCTL)) { + + if (!(tio->c_lflag & ECHOE)) + __ttyconf.magic |= kTtyNoEchoe; + if (!(tio->c_lflag & ECHOK)) + __ttyconf.magic |= kTtyNoEchok; + if (!(tio->c_lflag & ECHOKE)) + __ttyconf.magic |= kTtyNoEchoke; + if (!(tio->c_lflag & ECHOCTL)) __ttyconf.magic |= kTtyEchoRaw; - } + if (tio->c_lflag & ECHO) { // "kNtEnableEchoInput can be used only if the // kNtEnableLineInput mode is also enabled." -MSDN - if (tio->c_lflag & ICANON) { + if (tio->c_lflag & ICANON) inmode |= kNtEnableEchoInput; - } } else { __ttyconf.magic |= kTtySilence; } - if (!(tio->c_lflag & ISIG)) { + + if (!(tio->c_lflag & ISIG)) __ttyconf.magic |= kTtyNoIsigs; - } + + // IEXTEN enables implementation-defined input processing. This flag, + // as well as ICANON must be enabled for the special characters EOL2, + // LNEXT, REPRINT, WERASE to be interpreted. + if (!(tio->c_lflag & IEXTEN)) + __ttyconf.magic |= kTtyNoIexten; + memcpy(__ttyconf.c_cc, tio->c_cc, NCCS); - if ((tio->c_lflag & ISIG) && // - !(tio->c_lflag & ICANON) && // - __ttyconf.vintr == CTRL('C')) { + + if ((tio->c_lflag & ISIG) && __ttyconf.vintr == CTRL('C')) // allows ctrl-c to be delivered asynchronously via win32 // we normally don't want win32 doing this 24/7 in the bg // because we don't have job control, tcsetpgrp, etc. yet // it's normally much better to let read-nt.c raise a sig - // because read-nt only manages your tty whilst it's used + // because read-nt only manages your tty while it is used inmode |= kNtEnableProcessedInput; - } + outmode &= ~kNtDisableNewlineAutoReturn; outmode |= kNtEnableProcessedOutput; - if (!(tio->c_oflag & ONLCR)) { + if (!(tio->c_oflag & ONLCR)) outmode |= kNtDisableNewlineAutoReturn; - } outmode |= kNtEnableVirtualTerminalProcessing; // tune the win32 configuration diff --git a/libc/calls/timespec_mono.c b/libc/calls/timespec_mono.c index 044a3edfd01..4ca4fd2e7e3 100644 --- a/libc/calls/timespec_mono.c +++ b/libc/calls/timespec_mono.c @@ -29,6 +29,6 @@ */ struct timespec timespec_mono(void) { struct timespec ts; - npassert(!clock_gettime(CLOCK_MONOTONIC, &ts)); + unassert(!clock_gettime(CLOCK_MONOTONIC, &ts)); return ts; } diff --git a/libc/calls/unlinkat.c b/libc/calls/unlinkat.c index 54a3a5be672..b23830b59a8 100644 --- a/libc/calls/unlinkat.c +++ b/libc/calls/unlinkat.c @@ -39,12 +39,13 @@ * @param path is the thing to delete * @param flags can have AT_REMOVEDIR * @return 0 on success, or -1 w/ errno + * @raise EROFS if either path is under /zip/... */ int unlinkat(int dirfd, const char *path, int flags) { int rc; if (_weaken(__zipos_notat) && (rc = __zipos_notat(dirfd, path)) == -1) { - STRACE("zipos unlinkat not supported yet"); + rc = erofs(); } else if (!IsWindows()) { rc = sys_unlinkat(dirfd, path, flags); } else { diff --git a/libc/intrin/nomultics.h b/libc/intrin/nomultics.h index 833bc7e284d..b2aca3ecf8c 100644 --- a/libc/intrin/nomultics.h +++ b/libc/intrin/nomultics.h @@ -1,17 +1,21 @@ #ifndef COSMOPOLITAN_NOMULTICS_H_ #define COSMOPOLITAN_NOMULTICS_H_ -#define kTtySilence 1 /* do not relay read() into write() */ -#define kTtyEchoRaw 2 /* don't ^X visualize control codes */ -#define kTtyUncanon 4 /* enables non-canonical (raw) mode */ -#define kTtyNoCr2Nl 8 /* don't map \r → \n (a.k.a !ICRNL) */ -#define kTtyNoIsigs 16 /* don't auto-raise signals on keys */ -#define kTtyXtMouse 32 /* enables eXtreme Xterm mouse mode */ +#define kTtySilence 1 /* do not relay read() into write() */ +#define kTtyEchoRaw 2 /* don't ^X visualize control codes */ +#define kTtyUncanon 4 /* enables non-canonical (raw) mode */ +#define kTtyNoCr2Nl 8 /* don't map \r → \n (a.k.a !ICRNL) */ +#define kTtyNoIsigs 16 /* don't auto-raise signals on keys */ +#define kTtyXtMouse 32 /* enables eXtreme Xterm mouse mode */ +#define kTtyNoIexten 64 /* disable various canon keystrokes */ +#define kTtyNoEchoe 128 +#define kTtyNoEchok 256 +#define kTtyNoEchoke 512 COSMOPOLITAN_C_START_ struct TtyConf { - unsigned char magic; + unsigned magic; unsigned char mousebs; unsigned char replmode; unsigned char replstderr; diff --git a/libc/proc/fork.c b/libc/proc/fork.c index 79a12b61ccb..e201e712e2d 100644 --- a/libc/proc/fork.c +++ b/libc/proc/fork.c @@ -98,14 +98,14 @@ static int _forker(uint32_t dwCreationFlags) { struct timespec started; int ax, dx, tid, parent; parent = __pid; - started = timespec_real(); + started = timespec_mono(); _onfork_prepare(); if (!IsWindows()) { ax = sys_fork(); } else { ax = sys_fork_nt(dwCreationFlags); } - micros = timespec_tomicros(timespec_sub(timespec_real(), started)); + micros = timespec_tomicros(timespec_sub(timespec_mono(), started)); if (!ax) { // get new process id diff --git a/libc/proc/wait4-nt.c b/libc/proc/wait4-nt.c index 5f4a4f9d40e..a00830fa0cd 100644 --- a/libc/proc/wait4-nt.c +++ b/libc/proc/wait4-nt.c @@ -74,17 +74,14 @@ static textwindows int __proc_wait(int pid, int *wstatus, int options, // check for signals and cancelation int sig, handler_was_called; - if (_check_cancel() == -1) { + if (_check_cancel() == -1) return -1; - } if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) { handler_was_called = _weaken(__sig_relay)(sig, SI_KERNEL, waitmask); - if (_check_cancel() == -1) { + if (_check_cancel() == -1) return -1; // ECANCELED because SIGTHR was just handled - } - if (handler_was_called & SIG_HANDLED_NO_RESTART) { + if (handler_was_called & SIG_HANDLED_NO_RESTART) return eintr(); // a non-SA_RESTART handler was called - } } // check for zombie to harvest diff --git a/libc/runtime/zipos-notat.c b/libc/runtime/zipos-notat.c index 809d622df85..cebe2475708 100644 --- a/libc/runtime/zipos-notat.c +++ b/libc/runtime/zipos-notat.c @@ -23,9 +23,8 @@ int __zipos_notat(int dirfd, const char *path) { struct ZiposUri zipname; if (!path) - return efault(); - if (__isfdkind(dirfd, kFdZip) || __zipos_parseuri(path, &zipname) != -1) { - return einval(); - } + return 0; + if (__isfdkind(dirfd, kFdZip) || __zipos_parseuri(path, &zipname) != -1) + return -1; return 0; } diff --git a/libc/testlib/benchmark.h b/libc/testlib/benchmark.h index d416067112e..aef83f17d76 100644 --- a/libc/testlib/benchmark.h +++ b/libc/testlib/benchmark.h @@ -6,14 +6,14 @@ COSMOPOLITAN_C_START_ #define BENCHMARK(ITERATIONS, WORK_PER_RUN, CODE) \ do { \ - struct timespec start = timespec_real(); \ + struct timespec start = timespec_mono(); \ for (int __i = 0; __i < ITERATIONS; ++__i) { \ asm volatile("" ::: "memory"); \ CODE; \ } \ long long work = ((WORK_PER_RUN) ? (WORK_PER_RUN) : 1) * (ITERATIONS); \ double nanos = \ - (timespec_tonanos(timespec_sub(timespec_real(), start)) + work - 1) / \ + (timespec_tonanos(timespec_sub(timespec_mono(), start)) + work - 1) / \ (double)work; \ if (nanos < 1000) { \ printf("%10g ns %2dx %s\n", nanos, (ITERATIONS), #CODE); \ diff --git a/tool/build/runit.c b/tool/build/runit.c index 7d2b5346a78..5438669e3d7 100644 --- a/tool/build/runit.c +++ b/tool/build/runit.c @@ -161,12 +161,12 @@ void Connect(void) { CHECK_NE(-1, (g_sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol))); expo = INITIAL_CONNECT_TIMEOUT; - deadline = timespec_add(timespec_real(), + deadline = timespec_add(timespec_mono(), timespec_fromseconds(MAX_WAIT_CONNECT_SECONDS)); LOGIFNEG1(sigaction(SIGALRM, &(struct sigaction){.sa_handler = OnAlarm}, 0)); DEBUGF("connecting to %s (%hhu.%hhu.%hhu.%hhu) to run %s", g_hostname, ip4[0], ip4[1], ip4[2], ip4[3], g_prog); - struct timespec start = timespec_real(); + struct timespec start = timespec_mono(); TryAgain: alarmed = false; LOGIFNEG1(setitimer( @@ -178,7 +178,7 @@ void Connect(void) { if (rc == -1) { if (err == EINTR) { expo *= 1.5; - if (timespec_cmp(timespec_real(), deadline) >= 0) { + if (timespec_cmp(timespec_mono(), deadline) >= 0) { FATALF("timeout connecting to %s (%hhu.%hhu.%hhu.%hhu:%d)", g_hostname, ip4[0], ip4[1], ip4[2], ip4[3], ntohs(((struct sockaddr_in *)ai->ai_addr)->sin_port)); @@ -193,7 +193,7 @@ void Connect(void) { } setitimer(ITIMER_REAL, &(const struct itimerval){0}, 0); freeaddrinfo(ai); - connect_latency = timespec_tomicros(timespec_sub(timespec_real(), start)); + connect_latency = timespec_tomicros(timespec_sub(timespec_mono(), start)); } bool Send(int tmpfd, const void *output, size_t outputsize) { @@ -309,7 +309,7 @@ bool Recv(char *p, int n) { int ReadResponse(void) { int exitcode; - struct timespec start = timespec_real(); + struct timespec start = timespec_mono(); for (;;) { char msg[5]; if (!Recv(msg, 5)) { @@ -354,7 +354,7 @@ int ReadResponse(void) { break; } } - execute_latency = timespec_tomicros(timespec_sub(timespec_real(), start)); + execute_latency = timespec_tomicros(timespec_sub(timespec_mono(), start)); close(g_sock); return exitcode; } @@ -379,9 +379,9 @@ int RunOnHost(char *spec) { for (;;) { Connect(); EzFd(g_sock); - struct timespec start = timespec_real(); + struct timespec start = timespec_mono(); err = EzHandshake2(); - handshake_latency = timespec_tomicros(timespec_sub(timespec_real(), start)); + handshake_latency = timespec_tomicros(timespec_sub(timespec_mono(), start)); if (!err) break; WARNF("handshake with %s:%d failed -0x%04x (%s)", // diff --git a/tool/build/runitd.c b/tool/build/runitd.c index fbe2d4f62cf..0287bd29a37 100644 --- a/tool/build/runitd.c +++ b/tool/build/runitd.c @@ -453,8 +453,8 @@ void *ClientWorker(void *arg) { char *addrstr, *origname; unsigned char msg[4 + 1 + 4 + 4 + 4]; - ts0 = timespec_real(); - ts1 = timespec_real(); + ts0 = timespec_mono(); + ts1 = timespec_mono(); SetupPresharedKeySsl(MBEDTLS_SSL_IS_SERVER, g_psk); defer(FreeClient, client); @@ -466,14 +466,14 @@ void *ClientWorker(void *arg) { addrstr = DescribeAddress(&client->addr); DEBUF("%s %s %s", DescribeAddress(&g_servaddr), "accepted", addrstr); DEBUF("it took %'zu us to handshake client", - timespec_tomicros(timespec_sub(timespec_real(), ts1))); + timespec_tomicros(timespec_sub(timespec_mono(), ts1))); // get the executable - ts1 = timespec_real(); - ts2 = timespec_real(); + ts1 = timespec_mono(); + ts2 = timespec_mono(); Recv(client, msg, sizeof(msg)); DEBUF("it took %'zu us to receive #1", - timespec_tomicros(timespec_sub(timespec_real(), ts2))); + timespec_tomicros(timespec_sub(timespec_mono(), ts2))); if (READ32BE(msg) != RUNITD_MAGIC) { WARNF("%s magic mismatch!", addrstr); pthread_exit(0); @@ -486,19 +486,19 @@ void *ClientWorker(void *arg) { filesize = READ32BE(msg + 9); crc = READ32BE(msg + 13); origname = gc(calloc(1, namesize + 1)); - ts2 = timespec_real(); + ts2 = timespec_mono(); Recv(client, origname, namesize); DEBUF("it took %'zu us to receive #2", - timespec_tomicros(timespec_sub(timespec_real(), ts2))); + timespec_tomicros(timespec_sub(timespec_mono(), ts2))); VERBF("%s sent %#s (%'u bytes @ %#s)", addrstr, origname, filesize, client->tmpexepath); char *exedata = gc(malloc(filesize)); - ts2 = timespec_real(); + ts2 = timespec_mono(); Recv(client, exedata, filesize); DEBUF("it took %'zu us to receive #3", - timespec_tomicros(timespec_sub(timespec_real(), ts2))); + timespec_tomicros(timespec_sub(timespec_mono(), ts2))); DEBUF("it took %'zu us to receive executable from network", - timespec_tomicros(timespec_sub(timespec_real(), ts1))); + timespec_tomicros(timespec_sub(timespec_mono(), ts1))); if (crc32_z(0, exedata, filesize) != crc) { WARNF("%s crc mismatch! %#s", addrstr, origname); pthread_exit(0); @@ -509,7 +509,7 @@ void *ClientWorker(void *arg) { // condition can happen, where etxtbsy is raised by our execve // we're using o_cloexec so it's guaranteed to fix itself fast // thus we use an optimistic approach to avoid expensive locks - ts1 = timespec_real(); + ts1 = timespec_mono(); sprintf(client->tmpexepath, "o/%s.XXXXXX", basename(stripext(gc(strdup(origname))))); int exefd = openatemp(AT_FDCWD, client->tmpexepath, 0, O_CLOEXEC, 0700); @@ -533,7 +533,7 @@ void *ClientWorker(void *arg) { pthread_exit(0); } DEBUF("it took %'zu us to write executable to disk", - timespec_tomicros(timespec_sub(timespec_real(), ts1))); + timespec_tomicros(timespec_sub(timespec_mono(), ts1))); // do the args int i = 0; @@ -574,7 +574,7 @@ void *ClientWorker(void *arg) { posix_spawnattr_t spawnattr; posix_spawn_file_actions_t spawnfila; sigemptyset(&sigmask); - started = timespec_real(); + started = timespec_mono(); pipe2(client->pipe, O_CLOEXEC); posix_spawnattr_init(&spawnattr); posix_spawnattr_setflags(&spawnattr, @@ -584,11 +584,11 @@ void *ClientWorker(void *arg) { posix_spawn_file_actions_adddup2(&spawnfila, g_bogusfd, 0); posix_spawn_file_actions_adddup2(&spawnfila, client->pipe[1], 1); posix_spawn_file_actions_adddup2(&spawnfila, client->pipe[1], 2); - ts1 = timespec_real(); + ts1 = timespec_mono(); err = posix_spawn(&client->pid, client->tmpexepath, &spawnfila, &spawnattr, args, environ); DEBUF("it took %'zu us to call posix_spawn", - timespec_tomicros(timespec_sub(timespec_real(), ts1))); + timespec_tomicros(timespec_sub(timespec_mono(), ts1))); if (err) { if (err == ETXTBSY) { goto RetryOnEtxtbsyRaceCondition; @@ -603,7 +603,7 @@ void *ClientWorker(void *arg) { DEBUF("communicating %s[%d]", origname, client->pid); struct timespec deadline = - timespec_add(timespec_real(), timespec_fromseconds(DEATH_CLOCK_SECONDS)); + timespec_add(timespec_mono(), timespec_fromseconds(DEATH_CLOCK_SECONDS)); for (;;) { if (g_interrupted) { WARNF("killing %d %s and hanging up %d due to interrupt", client->fd, @@ -615,7 +615,7 @@ void *ClientWorker(void *arg) { PrintProgramOutput(client); pthread_exit(0); } - struct timespec now = timespec_real(); + struct timespec now = timespec_mono(); if (timespec_cmp(now, deadline) >= 0) { WARNF("killing %s (pid %d) which timed out after %d seconds", origname, client->pid, DEATH_CLOCK_SECONDS); @@ -626,11 +626,11 @@ void *ClientWorker(void *arg) { fds[0].events = POLLIN; fds[1].fd = client->pipe[0]; fds[1].events = POLLIN; - ts1 = timespec_real(); + ts1 = timespec_mono(); int64_t ms = timespec_tomillis(timespec_sub(deadline, now)); events = poll(fds, ARRAYLEN(fds), MIN(ms, -1u)); DEBUF("it took %'zu us to call poll", - timespec_tomicros(timespec_sub(timespec_real(), ts1))); + timespec_tomicros(timespec_sub(timespec_mono(), ts1))); if (events == -1) { if (errno == EINTR) { INFOF("poll interrupted"); @@ -645,10 +645,10 @@ void *ClientWorker(void *arg) { if (fds[0].revents) { int received; char buf[512]; - ts1 = timespec_real(); + ts1 = timespec_mono(); received = mbedtls_ssl_read(&ezssl, buf, sizeof(buf)); DEBUF("it took %'zu us to call mbedtls_ssl_read", - timespec_tomicros(timespec_sub(timespec_real(), ts1))); + timespec_tomicros(timespec_sub(timespec_mono(), ts1))); if (!received) { WARNF("%s client disconnected so killing worker %d", origname, client->pid); @@ -673,10 +673,10 @@ void *ClientWorker(void *arg) { } if (fds[1].revents) { char buf[512]; - ts1 = timespec_real(); + ts1 = timespec_mono(); ssize_t got = read(client->pipe[0], buf, sizeof(buf)); DEBUF("it took %'zu us to call read", - timespec_tomicros(timespec_sub(timespec_real(), ts1))); + timespec_tomicros(timespec_sub(timespec_mono(), ts1))); if (got == -1) { WARNF("got %s reading %s output", strerror(errno), origname); goto HangupClientAndTerminateJob; @@ -694,10 +694,10 @@ void *ClientWorker(void *arg) { WaitAgain: DEBUF("waitpid"); struct rusage rusage; - ts1 = timespec_real(); + ts1 = timespec_mono(); int wrc = wait4(client->pid, &wstatus, 0, &rusage); DEBUF("it took %'zu us to call wait4", - timespec_tomicros(timespec_sub(timespec_real(), ts1))); + timespec_tomicros(timespec_sub(timespec_mono(), ts1))); if (wrc == -1) { if (errno == EINTR) { WARNF("waitpid interrupted; killing %s pid %d", origname, client->pid); @@ -715,7 +715,7 @@ void *ClientWorker(void *arg) { } client->pid = 0; int exitcode; - struct timespec ended = timespec_real(); + struct timespec ended = timespec_mono(); int64_t micros = timespec_tomicros(timespec_sub(ended, started)); if (WIFEXITED(wstatus)) { if (WEXITSTATUS(wstatus)) { @@ -750,18 +750,18 @@ void *ClientWorker(void *arg) { AppendResourceReport(&client->output, &rusage, "\n"); PrintProgramOutput(client); } - ts1 = timespec_real(); + ts1 = timespec_mono(); SendProgramOutput(client); SendExitMessage(exitcode); mbedtls_ssl_close_notify(&ezssl); DEBUF("it took %'zu us to send result to client", - timespec_tomicros(timespec_sub(timespec_real(), ts1))); + timespec_tomicros(timespec_sub(timespec_mono(), ts1))); if (etxtbsy_tries > 1) { WARNF("encountered %d ETXTBSY race conditions spawning %s", etxtbsy_tries - 1, origname); } DEBUF("it took %'zu us TO DO EVERYTHING", - timespec_tomicros(timespec_sub(timespec_real(), ts0))); + timespec_tomicros(timespec_sub(timespec_mono(), ts0))); pthread_exit(0); } diff --git a/tool/viz/clock_nanosleep_accuracy.c b/tool/viz/clock_nanosleep_accuracy.c index b9a099fe781..358683e7994 100644 --- a/tool/viz/clock_nanosleep_accuracy.c +++ b/tool/viz/clock_nanosleep_accuracy.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" #include "libc/calls/struct/timespec.h" +#include "libc/intrin/describeflags.h" #include "libc/intrin/kprintf.h" #include "libc/runtime/runtime.h" #include "libc/stdio/stdio.h" @@ -26,36 +27,19 @@ #define MAXIMUM 1e9 #define ITERATIONS 10 -void TestSleepRealRelative(void) { +void TestSleepRelative(int clock) { printf("\n"); - printf("testing: clock_nanosleep(CLOCK_REALTIME) with relative " - "timeout\n"); + printf("testing: clock_nanosleep(%s) with relative timeout\n", + DescribeClockName(clock)); for (long nanos = 1; nanos < (long)MAXIMUM; nanos *= 2) { struct timespec t1, t2, wf; wf = timespec_fromnanos(nanos); - clock_gettime(CLOCK_REALTIME, &t1); + if (clock_gettime(clock, &t1)) + return; for (int i = 0; i < ITERATIONS; ++i) { - npassert(!clock_nanosleep(CLOCK_REALTIME, 0, &wf, 0)); + npassert(!clock_nanosleep(clock, 0, &wf, 0)); } - clock_gettime(CLOCK_REALTIME, &t2); - long took = timespec_tonanos(timespec_sub(t2, t1)) / ITERATIONS; - printf("%,12ld ns sleep took %,12ld ns delta %,12ld ns\n", nanos, took, - took - nanos); - } -} - -void TestSleepMonoRelative(void) { - printf("\n"); - printf("testing: clock_nanosleep(CLOCK_MONOTONIC) with relative " - "timeout\n"); - for (long nanos = 1; nanos < (long)MAXIMUM; nanos *= 2) { - struct timespec t1, t2, wf; - wf = timespec_fromnanos(nanos); - clock_gettime(CLOCK_REALTIME, &t1); - for (int i = 0; i < ITERATIONS; ++i) { - npassert(!clock_nanosleep(CLOCK_MONOTONIC, 0, &wf, 0)); - } - clock_gettime(CLOCK_REALTIME, &t2); + clock_gettime(clock, &t2); long took = timespec_tonanos(timespec_sub(t2, t1)) / ITERATIONS; printf("%,12ld ns sleep took %,12ld ns delta %,12ld ns\n", nanos, took, took - nanos); @@ -63,6 +47,8 @@ void TestSleepMonoRelative(void) { } int main(int argc, char *argv[]) { - TestSleepRealRelative(); - TestSleepMonoRelative(); + TestSleepRelative(CLOCK_REALTIME); + TestSleepRelative(CLOCK_MONOTONIC); + TestSleepRelative(CLOCK_REALTIME_COARSE); + TestSleepRelative(CLOCK_MONOTONIC_COARSE); }