diff --git a/libc/calls/ioctl_tcgets-nt.c b/libc/calls/ioctl_tcgets-nt.c index f5e3cae72..c3b0110ac 100644 --- a/libc/calls/ioctl_tcgets-nt.c +++ b/libc/calls/ioctl_tcgets-nt.c @@ -36,13 +36,23 @@ textwindows int ioctl_tcgets_nt(int ignored, struct termios *tio) { if (inok | outok) { bzero(tio, sizeof(*tio)); if (inok) { - if (inmode & kNtEnableLineInput) tio->c_lflag |= ICANON; - if (inmode & kNtEnableEchoInput) tio->c_lflag |= ECHO; - if (inmode & kNtEnableProcessedInput) tio->c_lflag |= IEXTEN | ISIG; + if (inmode & kNtEnableLineInput) { + tio->c_lflag |= ICANON; + } + if (inmode & kNtEnableEchoInput) { + tio->c_lflag |= ECHO; + } + if (inmode & kNtEnableProcessedInput) { + tio->c_lflag |= IEXTEN | ISIG; + } } if (outok) { - if (outmode & kNtEnableProcessedOutput) tio->c_oflag |= OPOST; - if (!(outmode & kNtDisableNewlineAutoReturn)) tio->c_oflag |= ONLCR; + if (outmode & kNtEnableProcessedOutput) { + tio->c_oflag |= OPOST; + } + if (!(outmode & kNtDisableNewlineAutoReturn)) { + tio->c_oflag |= ONLCR; + } } return 0; } else { diff --git a/libc/calls/ioctl_tcsets.c b/libc/calls/ioctl_tcsets.c index 4f665b2db..9ec4f832e 100644 --- a/libc/calls/ioctl_tcsets.c +++ b/libc/calls/ioctl_tcsets.c @@ -23,11 +23,10 @@ #include "libc/calls/termios.internal.h" #include "libc/dce.h" #include "libc/intrin/asan.internal.h" +#include "libc/intrin/nomultics.internal.h" #include "libc/sysv/consts/termios.h" #include "libc/sysv/errfuns.h" -extern bool __nomultics; - int ioctl_tcsets_nt(int, uint64_t, const struct termios *); static int ioctl_tcsets_metal(int fd, uint64_t request, @@ -85,7 +84,9 @@ int ioctl_tcsets(int fd, uint64_t request, ...) { rc = einval(); } if (rc != -1) { - __nomultics = !(tio->c_oflag & OPOST); + if (__nomultics == 0 || __nomultics == 1) { + __nomultics = !(tio->c_oflag & OPOST); + } } STRACE("ioctl_tcsets(%d, %p, %p) → %d% m", fd, request, tio, rc); return rc; diff --git a/libc/calls/poll.c b/libc/calls/poll.c index 0c81a093f..360ca5901 100644 --- a/libc/calls/poll.c +++ b/libc/calls/poll.c @@ -20,7 +20,11 @@ #include "libc/calls/internal.h" #include "libc/calls/strace.internal.h" #include "libc/dce.h" +#include "libc/errno.h" #include "libc/intrin/asan.internal.h" +#include "libc/intrin/describeflags.internal.h" +#include "libc/intrin/kprintf.h" +#include "libc/macros.internal.h" #include "libc/sock/internal.h" #include "libc/sock/sock.h" #include "libc/sysv/errfuns.h" @@ -62,8 +66,9 @@ * @norestart */ int poll(struct pollfd *fds, size_t nfds, int timeout_ms) { - int rc; + int i, rc; uint64_t millis; + if (IsAsan() && !__asan_is_valid(fds, nfds * sizeof(struct pollfd))) { rc = efault(); } else if (!IsWindows()) { @@ -76,6 +81,23 @@ int poll(struct pollfd *fds, size_t nfds, int timeout_ms) { millis = timeout_ms; rc = sys_poll_nt(fds, nfds, &millis); } - STRACE("poll(%p, %'lu, %'d) → %d% lm", fds, nfds, timeout_ms, rc); + +#if defined(SYSDEBUG) && _POLLTRACE + if (__strace > 0) { + if (rc == -1 && errno == EFAULT) { + STRACE("poll(%p, %'lu, %'d) → %d% lm", fds, nfds, timeout_ms, rc); + } else { + kprintf(STRACE_PROLOGUE "poll({"); + for (i = 0; i < MIN(5, nfds); ++i) { + kprintf("%s{%d,%s,%s}", i ? ", " : "", fds[i].fd, + DescribePollFlags(fds[i].events), + DescribePollFlags(fds[i].revents)); + } + kprintf("%s}, %'zu, %'d) → %d% lm%n", i == 5 ? "..." : "", nfds, + timeout_ms, rc); + } + } +#endif + return rc; } diff --git a/libc/calls/tcgetattr.c b/libc/calls/tcgetattr.c index a520fe460..e83c2d6d5 100644 --- a/libc/calls/tcgetattr.c +++ b/libc/calls/tcgetattr.c @@ -23,6 +23,32 @@ /** * Obtains the termios struct. * + * Here are the general defaults you can expect across platforms: + * + * c_iflag = ICRNL IXON + * c_oflag = OPOST ONLCR NL0 CR0 TAB0 BS0 VT0 FF0 + * c_cflag = ISIG CREAD CS8 + * c_lflag = ISIG ICANON ECHO ECHOE ECHOK IEXTEN ECHOCTL ECHOKE + * c_ispeed = 38400 + * c_ospeed = 38400 + * c_cc[VINTR] = CTRL-C + * c_cc[VQUIT] = CTRL-\ # ignore this comment + * c_cc[VERASE] = CTRL-? + * c_cc[VKILL] = CTRL-U + * c_cc[VEOF] = CTRL-D + * c_cc[VTIME] = CTRL-@ + * c_cc[VMIN] = CTRL-A + * c_cc[VSTART] = CTRL-Q + * c_cc[VSTOP] = CTRL-S + * c_cc[VSUSP] = CTRL-Z + * c_cc[VEOL] = CTRL-@ + * c_cc[VSWTC] = CTRL-@ + * c_cc[VREPRINT] = CTRL-R + * c_cc[VDISCARD] = CTRL-O + * c_cc[VWERASE] = CTRL-W + * c_cc[VLNEXT] = CTRL-V + * c_cc[VEOL2] = CTRL-@ + * * @param fd open file descriptor that isatty() * @param tio is where result is stored * @return -1 w/ errno on error diff --git a/libc/fmt/fmt.c b/libc/fmt/fmt.c index 42cb0aca5..692d5244e 100644 --- a/libc/fmt/fmt.c +++ b/libc/fmt/fmt.c @@ -24,6 +24,7 @@ #include "libc/fmt/fmts.h" #include "libc/fmt/internal.h" #include "libc/fmt/itoa.h" +#include "libc/intrin/nomultics.internal.h" #include "libc/macros.internal.h" #include "libc/mem/mem.h" #include "libc/nexgen32e/bsr.h" @@ -40,9 +41,6 @@ } \ } while (0) -extern bool __nomultics; -extern bool __replmode; - static const char kSpecialFloats[2][2][4] = {{"INF", "inf"}, {"NAN", "nan"}}; static void __fmt_free_dtoa(char **mem) { diff --git a/libc/intrin/asan.c b/libc/intrin/asan.c index 6fd7e194d..d5b104bb2 100644 --- a/libc/intrin/asan.c +++ b/libc/intrin/asan.c @@ -29,6 +29,7 @@ #include "libc/intrin/asancodes.h" #include "libc/intrin/kprintf.h" #include "libc/intrin/lockcmpxchg.h" +#include "libc/intrin/nomultics.internal.h" #include "libc/log/backtrace.internal.h" #include "libc/log/internal.h" #include "libc/log/libfatal.internal.h" @@ -153,7 +154,6 @@ struct ReportOriginHeap { }; bool __asan_noreentry; -extern bool __nomultics; static struct AsanMorgue __asan_morgue; static wontreturn void __asan_unreachable(void) { diff --git a/libc/intrin/describeflags.internal.h b/libc/intrin/describeflags.internal.h index cb9538119..ca790046a 100644 --- a/libc/intrin/describeflags.internal.h +++ b/libc/intrin/describeflags.internal.h @@ -14,6 +14,7 @@ const char *DescribeFlags(char *, size_t, struct DescribeFlags *, size_t, const char *DescribeMapFlags(int); const char *DescribeProtFlags(int); +const char *DescribePollFlags(int); const char *DescribeRemapFlags(int); const char *DescribeNtPageFlags(uint32_t); diff --git a/libc/intrin/describepollflags.greg.c b/libc/intrin/describepollflags.greg.c new file mode 100644 index 000000000..2e2a4d8a0 --- /dev/null +++ b/libc/intrin/describepollflags.greg.c @@ -0,0 +1,41 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2022 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/describeflags.internal.h" +#include "libc/macros.internal.h" +#include "libc/nt/enum/filemapflags.h" +#include "libc/sysv/consts/poll.h" + +const char *DescribePollFlags(int x) { + const struct DescribeFlags kPollFlags[] = { + {POLLIN, "IN"}, // order matters + {POLLOUT, "OUT"}, // order matters + {POLLPRI, "PRI"}, // + {POLLHUP, "HUP"}, // + {POLLERR, "ERR"}, // + {POLLNVAL, "NVAL"}, // + {POLLRDBAND, "RDBAND"}, // + {POLLRDHUP, "RDHUP"}, // + {POLLRDNORM, "RDNORM"}, // + {POLLWRBAND, "WRBAND"}, // + {POLLWRNORM, "WRNORM"}, // + }; + static char pollflags[64]; + return DescribeFlags(pollflags, sizeof(pollflags), kPollFlags, + ARRAYLEN(kPollFlags), "POLL", x); +} diff --git a/libc/intrin/kprintf.greg.c b/libc/intrin/kprintf.greg.c index 08364779e..e3456b9fc 100644 --- a/libc/intrin/kprintf.greg.c +++ b/libc/intrin/kprintf.greg.c @@ -29,6 +29,7 @@ #include "libc/intrin/cmpxchg.h" #include "libc/intrin/kprintf.h" #include "libc/intrin/lockcmpxchg.h" +#include "libc/intrin/nomultics.internal.h" #include "libc/intrin/spinlock.h" #include "libc/limits.h" #include "libc/macros.internal.h" @@ -54,8 +55,6 @@ struct Timestamps { }; extern int __pid; -extern bool __replmode; -extern bool __nomultics; unsigned long long __kbirth; // see fork-nt.c privileged static struct Timestamps kenter(void) { diff --git a/libc/intrin/nomultics.c b/libc/intrin/nomultics.c index 036f89f49..797465a4b 100644 --- a/libc/intrin/nomultics.c +++ b/libc/intrin/nomultics.c @@ -26,5 +26,13 @@ * * @see kprintf() */ -bool __nomultics; -bool __replmode; +char __nomultics; + +/** + * Controls ANSI prefix for log emissions. + * + * This should be true in raw tty mode repls. + * + * @see kprintf(), vflogf(), linenoise() + */ +char __replmode; diff --git a/libc/intrin/nomultics.internal.h b/libc/intrin/nomultics.internal.h new file mode 100644 index 000000000..73298e7d2 --- /dev/null +++ b/libc/intrin/nomultics.internal.h @@ -0,0 +1,11 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_NOMULTICS_INTERNAL_H_ +#define COSMOPOLITAN_LIBC_INTRIN_NOMULTICS_INTERNAL_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +extern bool __nomultics; +extern bool __replmode; + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_NOMULTICS_INTERNAL_H_ */ diff --git a/libc/log/log.h b/libc/log/log.h index 0d519b0f2..090a7a52b 100644 --- a/libc/log/log.h +++ b/libc/log/log.h @@ -4,6 +4,7 @@ #include "libc/calls/struct/rusage.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/struct/winsize.h" +#include "libc/errno.h" #include "libc/nexgen32e/stackframe.h" #include "libc/runtime/runtime.h" #include "libc/stdio/stdio.h" @@ -200,22 +201,26 @@ extern unsigned __log_level; /* log level for runtime check */ #define LOGIFNEG1(FORM) \ ({ \ + int e = errno; \ autotype(FORM) Ax = (FORM); \ if (UNLIKELY(Ax == (typeof(Ax))(-1)) && LOGGABLE(kLogWarn)) { \ ++g_ftrace; \ __logerrno(__FILE__, __LINE__, #FORM); \ --g_ftrace; \ + errno = e; \ } \ Ax; \ }) #define LOGIFNULL(FORM) \ ({ \ + int e = errno; \ autotype(FORM) Ax = (FORM); \ if (Ax == NULL && LOGGABLE(kLogWarn)) { \ ++g_ftrace; \ __logerrno(__FILE__, __LINE__, #FORM); \ --g_ftrace; \ + errno = e; \ } \ Ax; \ }) diff --git a/libc/log/vflogf.c b/libc/log/vflogf.c index 36b388753..ff4459909 100644 --- a/libc/log/vflogf.c +++ b/libc/log/vflogf.c @@ -54,7 +54,7 @@ void vflogf_onfail(FILE *f) { fseek(f, SEEK_SET, 0); f->beg = f->end = 0; clearerr(f); - (fprintf)(f, "performed emergency log truncation: %s\n", strerror(err)); + (fprintf)(f, "performed emergency log truncation: %s%n", strerror(err)); } } @@ -104,7 +104,7 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f, vflogf_onfail(f); } (vfprintf)(f, fmt, va); - fputs("\n", f); + fprintf(f, "%n"); if (bufmode == _IOLBF) { f->bufmode = _IOLBF; fflush(f); @@ -113,7 +113,7 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f, __start_fatal(file, line); strcpy(buf32, "unknown"); gethostname(buf32, sizeof(buf32)); - (dprintf)(STDERR_FILENO, "fatality %s pid %d\n", buf32, getpid()); + (dprintf)(STDERR_FILENO, "fatality %s pid %d%n", buf32, getpid()); __die(); unreachable; } diff --git a/libc/runtime/printargs.greg.c b/libc/runtime/printargs.greg.c index 47e8a736a..8e4728080 100644 --- a/libc/runtime/printargs.greg.c +++ b/libc/runtime/printargs.greg.c @@ -19,8 +19,12 @@ #include "libc/calls/calls.h" #include "libc/calls/strace.internal.h" #include "libc/calls/struct/sigset.h" +#include "libc/calls/struct/termios.h" +#include "libc/calls/termios.h" +#include "libc/calls/ttydefaults.h" #include "libc/dce.h" #include "libc/dns/dns.h" +#include "libc/errno.h" #include "libc/intrin/describeflags.internal.h" #include "libc/intrin/kprintf.h" #include "libc/macros.internal.h" @@ -44,6 +48,7 @@ #include "libc/sysv/consts/f.h" #include "libc/sysv/consts/poll.h" #include "libc/sysv/consts/sig.h" +#include "libc/sysv/consts/termios.h" #include "tool/decode/lib/idname.h" #include "tool/decode/lib/x86idnames.h" @@ -133,11 +138,13 @@ textstartup void __printargs(const char *prologue) { uintptr_t *auxp; struct utsname uts; char path[PATH_MAX]; - int x, st, ft, flags; + struct termios termios; + int e, x, st, ft, flags; struct pollfd pfds[128]; struct AuxiliaryValue *auxinfo; st = __strace, __strace = 0; ft = g_ftrace, g_ftrace = 0; + e = errno; PRINT(""); PRINT("SYSTEM"); @@ -327,6 +334,143 @@ textstartup void __printargs(const char *prologue) { PRINT("MEMTRACK"); PrintMemoryIntervals(2, &_mmi); + PRINT(""); + PRINT("TERMIOS"); + for (i = 0; i < 2; ++i) { + if (!tcgetattr(i, &termios)) { + PRINT(" - stdin"); + kprintf(prologue); + kprintf(" c_iflag ="); + if (termios.c_iflag & IGNBRK) kprintf(" IGNBRK"); + if (termios.c_iflag & BRKINT) kprintf(" BRKINT"); + if (termios.c_iflag & IGNPAR) kprintf(" IGNPAR"); + if (termios.c_iflag & PARMRK) kprintf(" PARMRK"); + if (termios.c_iflag & INPCK) kprintf(" INPCK"); + if (termios.c_iflag & ISTRIP) kprintf(" ISTRIP"); + if (termios.c_iflag & INLCR) kprintf(" INLCR"); + if (termios.c_iflag & IGNCR) kprintf(" IGNCR"); + if (termios.c_iflag & ICRNL) kprintf(" ICRNL"); + if (termios.c_iflag & IXON) kprintf(" IXON"); + if (termios.c_iflag & IXANY) kprintf(" IXANY"); + if (termios.c_iflag & IXOFF) kprintf(" IXOFF"); + if (termios.c_iflag & IMAXBEL) kprintf(" IMAXBEL"); + if (termios.c_iflag & IUTF8) kprintf(" IUTF8"); + if (termios.c_iflag & IUCLC) kprintf(" IUCLC"); + kprintf("%n"); + kprintf(prologue); + kprintf(" c_oflag ="); + if (termios.c_oflag & OPOST) kprintf(" OPOST"); + if (termios.c_oflag & ONLCR) kprintf(" ONLCR"); + if (termios.c_oflag & OCRNL) kprintf(" OCRNL"); + if (termios.c_oflag & ONOCR) kprintf(" ONOCR"); + if (termios.c_oflag & ONLRET) kprintf(" ONLRET"); + if (termios.c_oflag & OFILL) kprintf(" OFILL"); + if (termios.c_oflag & OFDEL) kprintf(" OFDEL"); + if (termios.c_oflag & OLCUC) kprintf(" OLCUC"); + if ((termios.c_oflag & NLDLY) == NL0) { + kprintf(" NL0"); + } else if ((termios.c_oflag & NLDLY) == NL1) { + kprintf(" NL1"); + } else if ((termios.c_oflag & NLDLY) == NL2) { + kprintf(" NL2"); + } else if ((termios.c_oflag & NLDLY) == NL3) { + kprintf(" NL3"); + } + if ((termios.c_oflag & CRDLY) == CR0) { + kprintf(" CR0"); + } else if ((termios.c_oflag & CRDLY) == CR1) { + kprintf(" CR1"); + } else if ((termios.c_oflag & CRDLY) == CR2) { + kprintf(" CR2"); + } else if ((termios.c_oflag & CRDLY) == CR3) { + kprintf(" CR3"); + } + if ((termios.c_oflag & TABDLY) == TAB0) { + kprintf(" TAB0"); + } else if ((termios.c_oflag & TABDLY) == TAB1) { + kprintf(" TAB1"); + } else if ((termios.c_oflag & TABDLY) == TAB2) { + kprintf(" TAB2"); + } else if ((termios.c_oflag & TABDLY) == TAB3) { + kprintf(" TAB3"); + } + if ((termios.c_oflag & BSDLY) == BS0) { + kprintf(" BS0"); + } else if ((termios.c_oflag & BSDLY) == BS1) { + kprintf(" BS1"); + } + if ((termios.c_oflag & VTDLY) == VT0) { + kprintf(" VT0"); + } else if ((termios.c_oflag & VTDLY) == VT1) { + kprintf(" VT1"); + } + if ((termios.c_oflag & FFDLY) == FF0) { + kprintf(" FF0"); + } else if ((termios.c_oflag & FFDLY) == FF1) { + kprintf(" FF1"); + } + kprintf("%n"); + kprintf(prologue); + kprintf(" c_cflag ="); + if (termios.c_cflag & ISIG) kprintf(" ISIG"); + if (termios.c_cflag & CSTOPB) kprintf(" CSTOPB"); + if (termios.c_cflag & CREAD) kprintf(" CREAD"); + if (termios.c_cflag & PARENB) kprintf(" PARENB"); + if (termios.c_cflag & PARODD) kprintf(" PARODD"); + if (termios.c_cflag & HUPCL) kprintf(" HUPCL"); + if (termios.c_cflag & CLOCAL) kprintf(" CLOCAL"); + if ((termios.c_cflag & CSIZE) == CS5) { + kprintf(" CS5"); + } else if ((termios.c_cflag & CSIZE) == CS6) { + kprintf(" CS6"); + } else if ((termios.c_cflag & CSIZE) == CS7) { + kprintf(" CS7"); + } else if ((termios.c_cflag & CSIZE) == CS8) { + kprintf(" CS8"); + } + kprintf("%n"); + kprintf(prologue); + kprintf(" c_lflag ="); + if (termios.c_lflag & ISIG) kprintf(" ISIG"); + if (termios.c_lflag & ICANON) kprintf(" ICANON"); + if (termios.c_lflag & ECHO) kprintf(" ECHO"); + if (termios.c_lflag & ECHOE) kprintf(" ECHOE"); + if (termios.c_lflag & ECHOK) kprintf(" ECHOK"); + if (termios.c_lflag & ECHONL) kprintf(" ECHONL"); + if (termios.c_lflag & NOFLSH) kprintf(" NOFLSH"); + if (termios.c_lflag & TOSTOP) kprintf(" TOSTOP"); + if (termios.c_lflag & IEXTEN) kprintf(" IEXTEN"); + if (termios.c_lflag & ECHOCTL) kprintf(" ECHOCTL"); + if (termios.c_lflag & ECHOPRT) kprintf(" ECHOPRT"); + if (termios.c_lflag & ECHOKE) kprintf(" ECHOKE"); + if (termios.c_lflag & FLUSHO) kprintf(" FLUSHO"); + if (termios.c_lflag & PENDIN) kprintf(" PENDIN"); + if (termios.c_lflag & XCASE) kprintf(" XCASE"); + kprintf("%n"); + PRINT(" c_ispeed = %u", termios.c_ispeed); + PRINT(" c_ospeed = %u", termios.c_ospeed); + PRINT(" c_cc[VINTR] = CTRL-%c", CTRL(termios.c_cc[VINTR])); + PRINT(" c_cc[VQUIT] = CTRL-%c", CTRL(termios.c_cc[VQUIT])); + PRINT(" c_cc[VERASE] = CTRL-%c", CTRL(termios.c_cc[VERASE])); + PRINT(" c_cc[VKILL] = CTRL-%c", CTRL(termios.c_cc[VKILL])); + PRINT(" c_cc[VEOF] = CTRL-%c", CTRL(termios.c_cc[VEOF])); + PRINT(" c_cc[VTIME] = CTRL-%c", CTRL(termios.c_cc[VTIME])); + PRINT(" c_cc[VMIN] = CTRL-%c", CTRL(termios.c_cc[VMIN])); + PRINT(" c_cc[VSTART] = CTRL-%c", CTRL(termios.c_cc[VSTART])); + PRINT(" c_cc[VSTOP] = CTRL-%c", CTRL(termios.c_cc[VSTOP])); + PRINT(" c_cc[VSUSP] = CTRL-%c", CTRL(termios.c_cc[VSUSP])); + PRINT(" c_cc[VEOL] = CTRL-%c", CTRL(termios.c_cc[VEOL])); + PRINT(" c_cc[VSWTC] = CTRL-%c", CTRL(termios.c_cc[VSWTC])); + PRINT(" c_cc[VREPRINT] = CTRL-%c", CTRL(termios.c_cc[VREPRINT])); + PRINT(" c_cc[VDISCARD] = CTRL-%c", CTRL(termios.c_cc[VDISCARD])); + PRINT(" c_cc[VWERASE] = CTRL-%c", CTRL(termios.c_cc[VWERASE])); + PRINT(" c_cc[VLNEXT] = CTRL-%c", CTRL(termios.c_cc[VLNEXT])); + PRINT(" c_cc[VEOL2] = CTRL-%c", CTRL(termios.c_cc[VEOL2])); + } else { + PRINT(" - tcgetattr(%d) failed %m", i); + } + } + if (IsWindows()) { struct NtStartupInfo startinfo; GetStartupInfo(&startinfo); @@ -400,4 +544,5 @@ textstartup void __printargs(const char *prologue) { PRINT(""); __strace = st; g_ftrace = ft; + errno = e; } diff --git a/libc/runtime/winmain.greg.c b/libc/runtime/winmain.greg.c index f28852cc3..eb0347694 100644 --- a/libc/runtime/winmain.greg.c +++ b/libc/runtime/winmain.greg.c @@ -26,6 +26,7 @@ #include "libc/errno.h" #include "libc/fmt/fmt.h" #include "libc/intrin/kprintf.h" +#include "libc/intrin/nomultics.internal.h" #include "libc/log/libfatal.internal.h" #include "libc/macros.internal.h" #include "libc/nexgen32e/bsr.h" @@ -86,7 +87,6 @@ struct WinArgs { }; extern int __pid; -extern bool __nomultics; extern uint32_t __winmainpid; extern int64_t __wincrashearly; extern const char kConsoleHandles[3]; diff --git a/libc/str/startswithi.c b/libc/str/startswithi.c index 737280807..52a14c949 100644 --- a/libc/str/startswithi.c +++ b/libc/str/startswithi.c @@ -22,6 +22,6 @@ bool startswithi(const char *s, const char *prefix) { for (;;) { if (!*prefix) return true; if (!*s) return false; - if (kToLower[*s++ & 255] != (*prefix++ & 255)) return false; + if (kToLower[*s++ & 255] != kToLower[*prefix++ & 255]) return false; } } diff --git a/third_party/linenoise/linenoise.c b/third_party/linenoise/linenoise.c index 3a04e79d4..7aac8ab61 100644 --- a/third_party/linenoise/linenoise.c +++ b/third_party/linenoise/linenoise.c @@ -122,7 +122,9 @@ #include "libc/assert.h" #include "libc/bits/bits.h" #include "libc/calls/calls.h" +#include "libc/calls/sig.internal.h" #include "libc/calls/sigbits.h" +#include "libc/calls/strace.internal.h" #include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/stat.h" #include "libc/calls/termios.h" @@ -131,6 +133,9 @@ #include "libc/errno.h" #include "libc/fmt/conv.h" #include "libc/intrin/asan.internal.h" +#include "libc/intrin/kprintf.h" +#include "libc/intrin/nomultics.internal.h" +#include "libc/log/check.h" #include "libc/log/log.h" #include "libc/macros.internal.h" #include "libc/mem/mem.h" @@ -160,6 +165,8 @@ Copyright 2018-2020 Justine Tunney \\n\ Copyright 2010-2016 Salvatore Sanfilippo \\n\ Copyright 2010-2013 Pieter Noordhuis \""); +#define LINENOISE_POLL_MS __SIG_POLLING_INTERVAL_MS + #define LINENOISE_MAX_RING 8 #define LINENOISE_MAX_DEBUG 16 #define LINENOISE_MAX_HISTORY 1024 @@ -249,7 +256,6 @@ static char maskmode; static char ispaused; static char iscapital; static int historylen; -extern bool __replmode; static struct linenoiseRing ring; static struct sigaction orig_int; static struct sigaction orig_quit; @@ -257,6 +263,7 @@ static struct sigaction orig_cont; static struct sigaction orig_winch; static struct termios orig_termios; static char *history[LINENOISE_MAX_HISTORY]; +static linenoisePollCallback *pollCallback; static linenoiseXlatCallback *xlatCallback; static linenoiseHintsCallback *hintsCallback; static linenoiseFreeHintsCallback *freeHintsCallback; @@ -703,6 +710,22 @@ static void linenoiseDebug(struct linenoiseState *l, const char *fmt, ...) { free(msg); } +static int linenoisePoll(struct linenoiseState *l, int fd) { + int rc, ms; + for (ms = LINENOISE_POLL_MS;;) { + if (pollCallback) { + rc = pollCallback(fd, ms); + } else { + rc = poll((struct pollfd[]){{fd, POLLIN}}, 1, ms); + } + if (rc) { + return rc; + } else { + linenoiseRefreshLine(l); + } + } +} + static ssize_t linenoiseRead(int fd, char *buf, size_t size, struct linenoiseState *l) { ssize_t rc; @@ -719,6 +742,7 @@ static ssize_t linenoiseRead(int fd, char *buf, size_t size, } if (l && gotwinch) refreshme = 1; if (refreshme) linenoiseRefreshLine(l); + if (linenoisePoll(l, fd) == -1) return -1; rc = readansi(fd, buf, size); } while (rc == -1 && errno == EINTR); if (l && rc > 0) { @@ -777,7 +801,7 @@ void linenoiseClearScreen(int fd) { } static void linenoiseBeep(void) { - /* THE TERMINAL BELL IS DEAD - HISTORY HAS KILLED IT */ + // THE TERMINAL BELL IS DEAD - HISTORY HAS KILLED IT } static char linenoiseGrow(struct linenoiseState *ls, size_t n) { @@ -829,27 +853,31 @@ static ssize_t linenoiseCompleteLine(struct linenoiseState *ls, char *seq, // if there's a multiline completions, then do nothing and wait and // see if the user presses tab again. if the user does this we then // print ALL the completions, to above the editing line - nread = linenoiseRead(ls->ifd, seq, size, ls); - if (nread == 1 && seq[0] == '\t') { - itemlen = linenoiseMaxCompletionLength(&lc) + 4; - perline = MAX(1, (ls->ws.ws_col - 1) / itemlen); - abInit(&ab); - abAppends(&ab, "\r\033[K"); - for (i = 0; i < lc.len;) { - for (j = 0; i < lc.len && j < perline; ++j, ++i) { - n = GetMonospaceWidth(lc.cvec[i], strlen(lc.cvec[i]), 0); - abAppends(&ab, lc.cvec[i]); - for (k = n; k < itemlen; ++k) { - abAppendw(&ab, ' '); + for (;;) { + nread = linenoiseRead(ls->ifd, seq, size, ls); + if (nread == 1 && seq[0] == '\t') { + itemlen = linenoiseMaxCompletionLength(&lc) + 4; + perline = MAX(1, (ls->ws.ws_col - 1) / itemlen); + abInit(&ab); + abAppends(&ab, "\r\n\033[K"); + for (i = 0; i < lc.len;) { + for (j = 0; i < lc.len && j < perline; ++j, ++i) { + n = GetMonospaceWidth(lc.cvec[i], strlen(lc.cvec[i]), 0); + abAppends(&ab, lc.cvec[i]); + for (k = n; k < itemlen; ++k) { + abAppendw(&ab, ' '); + } } + abAppendw(&ab, READ16LE("\r\n")); } - abAppendw(&ab, READ16LE("\r\n")); + ab.len -= 2; + abAppends(&ab, "\n"); + linenoiseWriteStr(ls->ofd, ab.b); + linenoiseRefreshLine(ls); + abFree(&ab); + } else { + break; } - ab.len -= 2; - abAppends(&ab, "\n"); - linenoiseWriteStr(ls->ofd, ab.b); - linenoiseRefreshLine(ls); - abFree(&ab); } } linenoiseFreeCompletions(&lc); @@ -1697,6 +1725,7 @@ static void linenoiseEditCtrlq(struct linenoiseState *l) { */ static ssize_t linenoiseEdit(int stdin_fd, int stdout_fd, const char *prompt, char **obuf) { + int st; ssize_t rc; uint64_t w; size_t nread; @@ -2108,6 +2137,7 @@ char *linenoise(const char *prompt) { * however if it contains a slash / dot then we'll assume prog is * the history filename which as determined by the caller * @return chomped allocated string of read line or null on eof/error + * noting that on eof your errno is not changed */ char *linenoiseWithHistory(const char *prompt, const char *prog) { char *line, *res; @@ -2137,7 +2167,9 @@ char *linenoiseWithHistory(const char *prompt, const char *prog) { } } if (path.len) { - linenoiseHistoryLoad(path.b); + if (linenoiseHistoryLoad(path.b) == -1) { + kprintf("%r%s: failed to load history: %m%n", path); + } } line = linenoise(prompt); if (path.len && line && *line) { @@ -2185,6 +2217,13 @@ void linenoiseSetXlatCallback(linenoiseXlatCallback *fn) { xlatCallback = fn; } +/** + * Sets terminal fd pollin callback. + */ +void linenoiseSetPollCallback(linenoisePollCallback *fn) { + pollCallback = fn; +} + /** * Adds completion. * diff --git a/third_party/linenoise/linenoise.h b/third_party/linenoise/linenoise.h index 2e8aed923..dd8cf0cfe 100644 --- a/third_party/linenoise/linenoise.h +++ b/third_party/linenoise/linenoise.h @@ -13,12 +13,14 @@ typedef char *(linenoiseHintsCallback)(const char *, const char **, const char **); typedef void(linenoiseFreeHintsCallback)(void *); typedef wint_t(linenoiseXlatCallback)(wint_t); +typedef int(linenoisePollCallback)(int, int); void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); void linenoiseSetHintsCallback(linenoiseHintsCallback *); void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); void linenoiseAddCompletion(linenoiseCompletions *, const char *); void linenoiseSetXlatCallback(linenoiseXlatCallback *); +void linenoiseSetPollCallback(linenoisePollCallback *); char *linenoise(const char *) dontdiscard; char *linenoiseRaw(const char *, int, int) dontdiscard; diff --git a/third_party/lua/lrepl.c b/third_party/lua/lrepl.c new file mode 100644 index 000000000..3af46ec6f --- /dev/null +++ b/third_party/lua/lrepl.c @@ -0,0 +1,296 @@ +#define lua_c +#include "libc/calls/calls.h" +#include "libc/calls/sigbits.h" +#include "libc/log/check.h" +#include "libc/runtime/gc.h" +#include "libc/runtime/runtime.h" +#include "libc/sysv/consts/sa.h" +#include "libc/x/x.h" +#include "third_party/linenoise/linenoise.h" +#include "third_party/lua/lauxlib.h" +#include "third_party/lua/lprefix.h" +#include "third_party/lua/lua.h" +#include "third_party/lua/lualib.h" +// clang-format off + +static lua_State *globalL; +static const char *g_progname; + +/* +** {================================================================== +** Read-Eval-Print Loop (REPL) +** =================================================================== +*/ + +#if !defined(LUA_PROMPT) +#define LUA_PROMPT ">: " +#define LUA_PROMPT2 ">>: " +#endif + +#if !defined(LUA_MAXINPUT) +#define LUA_MAXINPUT 512 +#endif + +static void lua_readline_addcompletion(linenoiseCompletions *c, char *s) { + char **p = c->cvec; + size_t n = c->len + 1; + if ((p = realloc(p, n * sizeof(*p)))) { + p[n - 1] = s; + c->cvec = p; + c->len = n; + } +} + +void lua_readline_completions(const char *p, linenoiseCompletions *c) { + lua_State *L; + const char *name; + L = globalL; + lua_pushglobaltable(L); + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + name = lua_tostring(L, -2); + if (startswithi(name, p)) { + lua_readline_addcompletion(c, strdup(name)); + } + lua_pop(L, 1); + } + lua_pop(L, 1); +} + +char *lua_readline_hint(const char *p, const char **ansi1, const char **ansi2) { + char *h = 0; + linenoiseCompletions c = {0}; + lua_readline_completions(p, &c); + if (c.len == 1) h = strdup(c.cvec[0] + strlen(p)); + linenoiseFreeCompletions(&c); + return h; +} + +static void lua_freeline (lua_State *L, char *b) { + free(b); +} + +/* +** Return the string to be used as a prompt by the interpreter. Leave +** the string (or nil, if using the default value) on the stack, to keep +** it anchored. +*/ +static const char *get_prompt (lua_State *L, int firstline) { + if (lua_getglobal(L, firstline ? "_PROMPT" : "_PROMPT2") == LUA_TNIL) + return (firstline ? LUA_PROMPT : LUA_PROMPT2); /* use the default */ + else { /* apply 'tostring' over the value */ + const char *p = luaL_tolstring(L, -1, NULL); + lua_remove(L, -2); /* remove original value */ + return p; + } +} + +/* mark in error messages for incomplete statements */ +#define EOFMARK "" +#define marklen (sizeof(EOFMARK)/sizeof(char) - 1) + + +/* +** Check whether 'status' signals a syntax error and the error +** message at the top of the stack ends with the above mark for +** incomplete statements. +*/ +static int incomplete (lua_State *L, int status) { + if (status == LUA_ERRSYNTAX) { + size_t lmsg; + const char *msg = lua_tolstring(L, -1, &lmsg); + if (lmsg >= marklen && strcmp(msg + lmsg - marklen, EOFMARK) == 0) { + lua_pop(L, 1); + return 1; + } + } + return 0; /* else... */ +} + + +/* +** Prompt the user, read a line, and push it into the Lua stack. +*/ +static int pushline (lua_State *L, int firstline) { + char *b; + size_t l; + globalL = L; + const char *prmt = get_prompt(L, firstline); + if (!(b = linenoiseWithHistory(prmt, g_progname))) + return 0; /* no input (prompt will be popped by caller) */ + lua_pop(L, 1); /* remove prompt */ + l = strlen(b); + if (l > 0 && b[l-1] == '\n') /* line ends with newline? */ + b[--l] = '\0'; /* remove it */ + if (firstline && b[0] == '=') /* for compatibility with 5.2, ... */ + lua_pushfstring(L, "return %s", b + 1); /* change '=' to 'return' */ + else + lua_pushlstring(L, b, l); + lua_freeline(L, b); + return 1; +} + + +/* +** Try to compile line on the stack as 'return ;'; on return, stack +** has either compiled chunk or original line (if compilation failed). +*/ +static int addreturn (lua_State *L) { + const char *line = lua_tostring(L, -1); /* original line */ + const char *retline = lua_pushfstring(L, "return %s;", line); + int status = luaL_loadbuffer(L, retline, strlen(retline), "=stdin"); + if (status == LUA_OK) { + lua_remove(L, -2); /* remove modified line */ + } else { + lua_pop(L, 2); /* pop result from 'luaL_loadbuffer' and modified line */ + } + return status; +} + + +/* +** Hook set by signal function to stop the interpreter. +*/ +static void lstop (lua_State *L, lua_Debug *ar) { + (void)ar; /* unused arg. */ + lua_sethook(L, NULL, 0, 0); /* reset hook */ + luaL_error(L, "interrupted!"); +} + + +/* +** Read multiple lines until a complete Lua statement +*/ +static int multiline (lua_State *L) { + for (;;) { /* repeat until gets a complete statement */ + size_t len; + const char *line = lua_tolstring(L, 1, &len); /* get what it has */ + int status = luaL_loadbuffer(L, line, len, "=stdin"); /* try it */ + if (!incomplete(L, status) || !pushline(L, 0)) { + return status; /* cannot or should not try to add continuation line */ + } + lua_pushliteral(L, "\n"); /* add newline... */ + lua_insert(L, -2); /* ...between the two lines */ + lua_concat(L, 3); /* join them */ + } +} + + +void lua_initrepl(const char *progname) { + g_progname = progname; + linenoiseSetCompletionCallback(lua_readline_completions); + linenoiseSetHintsCallback(lua_readline_hint); + linenoiseSetFreeHintsCallback(free); +} + + +/* +** Read a line and try to load (compile) it first as an expression (by +** adding "return " in front of it) and second as a statement. Return +** the final status of load/call with the resulting function (if any) +** in the top of the stack. +*/ +int lua_loadline (lua_State *L) { + int status; + lua_settop(L, 0); + if (!pushline(L, 1)) + return -1; /* no input */ + if ((status = addreturn(L)) != LUA_OK) /* 'return ...' did not work? */ + status = multiline(L); /* try as command, maybe with continuation lines */ + lua_remove(L, 1); /* remove line from the stack */ + lua_assert(lua_gettop(L) == 1); + return status; +} + + +/* +** Function to be called at a C signal. Because a C signal cannot +** just change a Lua state (as there is no proper synchronization), +** this function only sets a hook that, when called, will stop the +** interpreter. +*/ +static void laction (int i) { + int flag = LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE | LUA_MASKCOUNT; + lua_sethook(globalL, lstop, flag, 1); +} + + +/* +** Message handler used to run all chunks +*/ +static int msghandler (lua_State *L) { + const char *msg = lua_tostring(L, 1); + if (msg == NULL) { /* is error object not a string? */ + if (luaL_callmeta(L, 1, "__tostring") && /* does it have a metamethod */ + lua_type(L, -1) == LUA_TSTRING) /* that produces a string? */ + return 1; /* that is the message */ + else + msg = lua_pushfstring(L, "(error object is a %s value)", + luaL_typename(L, 1)); + } + luaL_traceback(L, L, msg, 1); /* append a standard traceback */ + return 1; /* return the traceback */ +} + + +/* +** Interface to 'lua_pcall', which sets appropriate message function +** and C-signal handler. Used to run all chunks. +*/ +int lua_runchunk (lua_State *L, int narg, int nres) { + struct sigaction sa, saold; + int status; + int base = lua_gettop(L) - narg; /* function index */ + lua_pushcfunction(L, msghandler); /* push message handler */ + lua_insert(L, base); /* put it under function and args */ + globalL = L; /* to be available to 'laction' */ + sa.sa_flags = SA_RESETHAND; /* if another int happens, terminate */ + sa.sa_handler = laction; + sigemptyset(&sa.sa_mask); /* do not mask any signal */ + sigaction(SIGINT, &sa, &saold); + status = lua_pcall(L, narg, nres, base); + sigaction(SIGINT, &saold, 0); /* restore C-signal handler */ + lua_remove(L, base); /* remove message handler from the stack */ + return status; +} + + +/* +** Prints an error message, adding the program name in front of it +** (if present) +*/ +void lua_l_message (const char *pname, const char *msg) { + if (pname) lua_writestringerror("%s: ", pname); + lua_writestringerror("%s\n", msg); +} + + +/* +** Prints (calling the Lua 'print' function) any values on the stack +*/ +void lua_l_print (lua_State *L) { + int n = lua_gettop(L); + if (n > 0) { /* any result to be printed? */ + luaL_checkstack(L, LUA_MINSTACK, "too many results to print"); + lua_getglobal(L, "print"); + lua_insert(L, 1); + if (lua_pcall(L, n, 0, 0) != LUA_OK) + lua_l_message(g_progname, lua_pushfstring(L, "error calling 'print' (%s)", + lua_tostring(L, -1))); + } +} + + +/* +** Check whether 'status' is not OK and, if so, prints the error +** message on the top of the stack. It assumes that the error object +** is a string, as it was either generated by Lua or by 'msghandler'. +*/ +int lua_report (lua_State *L, int status) { + if (status != LUA_OK) { + const char *msg = lua_tostring(L, -1); + lua_l_message(g_progname, msg); + lua_pop(L, 1); /* remove message */ + } + return status; +} diff --git a/third_party/lua/lrepl.h b/third_party/lua/lrepl.h new file mode 100644 index 000000000..2ac8d6629 --- /dev/null +++ b/third_party/lua/lrepl.h @@ -0,0 +1,19 @@ +#ifndef COSMOPOLITAN_THIRD_PARTY_LUA_LREPL_H_ +#define COSMOPOLITAN_THIRD_PARTY_LUA_LREPL_H_ +#include "third_party/linenoise/linenoise.h" +#include "third_party/lua/lauxlib.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +int lua_loadline(lua_State *); +void lua_l_print(lua_State *); +void lua_initrepl(const char *); +int lua_report(lua_State *, int); +int lua_runchunk(lua_State *, int, int); +void lua_l_message(const char *, const char *); +char *lua_readline_hint(const char *, const char **, const char **); +void lua_readline_completions(const char *, linenoiseCompletions *); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_THIRD_PARTY_LUA_LREPL_H_ */ diff --git a/third_party/lua/lua.main.c b/third_party/lua/lua.main.c index efb67e72a..22d91550e 100644 --- a/third_party/lua/lua.main.c +++ b/third_party/lua/lua.main.c @@ -13,11 +13,14 @@ #include "libc/log/log.h" #include "libc/runtime/gc.h" #include "libc/runtime/stack.h" +#include "libc/str/str.h" #include "libc/sysv/consts/exit.h" +#include "libc/sysv/consts/sa.h" #include "libc/x/x.h" #include "third_party/linenoise/linenoise.h" #include "third_party/lua/lauxlib.h" #include "third_party/lua/lprefix.h" +#include "third_party/lua/lrepl.h" #include "third_party/lua/lua.h" #include "third_party/lua/lualib.h" @@ -38,49 +41,10 @@ STATIC_STACK_SIZE(0x40000); static lua_State *globalL = NULL; static const char *progname = LUA_PROGNAME; -static const char *histpath; -#if defined(LUA_USE_POSIX) /* { */ - -/* -** Use 'sigaction' when available. -*/ -static void setsignal (int sig, void (*handler)(int)) { - struct sigaction sa; - sa.sa_handler = handler; - sa.sa_flags = 0; - sigemptyset(&sa.sa_mask); /* do not mask any signal */ - sigaction(sig, &sa, NULL); -} - -#else /* }{ */ - -#define setsignal signal - -#endif /* } */ - - -/* -** Hook set by signal function to stop the interpreter. -*/ -static void lstop (lua_State *L, lua_Debug *ar) { - (void)ar; /* unused arg. */ - lua_sethook(L, NULL, 0, 0); /* reset hook */ - luaL_error(L, "interrupted!"); -} - - -/* -** Function to be called at a C signal. Because a C signal cannot -** just change a Lua state (as there is no proper synchronization), -** this function only sets a hook that, when called, will stop the -** interpreter. -*/ -static void laction (int i) { - int flag = LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE | LUA_MASKCOUNT; - setsignal(i, SIG_DFL); /* if another SIGINT happens, terminate process */ - lua_sethook(globalL, lstop, flag, 1); +static bool lua_stdin_is_tty(void) { + return isatty(0); } @@ -106,67 +70,6 @@ static void print_usage (const char *badoption) { } -/* -** Prints an error message, adding the program name in front of it -** (if present) -*/ -static void l_message (const char *pname, const char *msg) { - if (pname) lua_writestringerror("%s: ", pname); - lua_writestringerror("%s\n", msg); -} - - -/* -** Check whether 'status' is not OK and, if so, prints the error -** message on the top of the stack. It assumes that the error object -** is a string, as it was either generated by Lua or by 'msghandler'. -*/ -static int report (lua_State *L, int status) { - if (status != LUA_OK) { - const char *msg = lua_tostring(L, -1); - l_message(progname, msg); - lua_pop(L, 1); /* remove message */ - } - return status; -} - - -/* -** Message handler used to run all chunks -*/ -static int msghandler (lua_State *L) { - const char *msg = lua_tostring(L, 1); - if (msg == NULL) { /* is error object not a string? */ - if (luaL_callmeta(L, 1, "__tostring") && /* does it have a metamethod */ - lua_type(L, -1) == LUA_TSTRING) /* that produces a string? */ - return 1; /* that is the message */ - else - msg = lua_pushfstring(L, "(error object is a %s value)", - luaL_typename(L, 1)); - } - luaL_traceback(L, L, msg, 1); /* append a standard traceback */ - return 1; /* return the traceback */ -} - - -/* -** Interface to 'lua_pcall', which sets appropriate message function -** and C-signal handler. Used to run all chunks. -*/ -static int docall (lua_State *L, int narg, int nres) { - int status; - int base = lua_gettop(L) - narg; /* function index */ - lua_pushcfunction(L, msghandler); /* push message handler */ - lua_insert(L, base); /* put it under function and args */ - globalL = L; /* to be available to 'laction' */ - setsignal(SIGINT, laction); /* set C-signal handler */ - status = lua_pcall(L, narg, nres, base); - setsignal(SIGINT, SIG_DFL); /* reset C-signal handler */ - lua_remove(L, base); /* remove message handler from the stack */ - return status; -} - - static void print_version (void) { lua_writestring(LUA_COPYRIGHT, strlen(LUA_COPYRIGHT)); lua_writeline(); @@ -195,8 +98,8 @@ static void createargtable (lua_State *L, char **argv, int argc, int script) { static int dochunk (lua_State *L, int status) { - if (status == LUA_OK) status = docall(L, 0, 0); - return report(L, status); + if (status == LUA_OK) status = lua_runchunk(L, 0, 0); + return lua_report(L, status); } @@ -218,10 +121,10 @@ static int dolibrary (lua_State *L, const char *name) { int status; lua_getglobal(L, "require"); lua_pushstring(L, name); - status = docall(L, 1, 1); /* call 'require(name)' */ + status = lua_runchunk(L, 1, 1); /* call 'require(name)' */ if (status == LUA_OK) lua_setglobal(L, name); /* global[name] = require return */ - return report(L, status); + return lua_report(L, status); } @@ -249,9 +152,9 @@ static int handle_script (lua_State *L, char **argv) { status = luaL_loadfile(L, fname); if (status == LUA_OK) { int n = pushargs(L); /* push arguments to script */ - status = docall(L, n, LUA_MULTRET); + status = lua_runchunk(L, n, LUA_MULTRET); } - return report(L, status); + return lua_report(L, status); } @@ -364,225 +267,6 @@ static int handle_luainit (lua_State *L) { } -/* -** {================================================================== -** Read-Eval-Print Loop (REPL) -** =================================================================== -*/ - -#if !defined(LUA_PROMPT) -#define LUA_PROMPT ">: " -#define LUA_PROMPT2 ">>: " -#endif - -#if !defined(LUA_MAXINPUT) -#define LUA_MAXINPUT 512 -#endif - -static bool lua_stdin_is_tty(void) { - return isatty(0); -} - -static bool lua_istartswith(const char *s, const char *prefix) { - for (;;) { - if (!*prefix) return true; - if (!*s) return false; - if (tolower(*s++) != tolower(*prefix++)) return false; - } -} - -static void lua_readline_addcompletion(linenoiseCompletions *c, char *s) { - char **p = c->cvec; - size_t n = c->len + 1; - if ((p = realloc(p, n * sizeof(*p)))) { - p[n - 1] = s; - c->cvec = p; - c->len = n; - } -} - -static void lua_readline_completions(const char *p, linenoiseCompletions *c) { - lua_State *L; - const char *name; - L = globalL; - lua_pushglobaltable(L); - lua_pushnil(L); - while (lua_next(L, -2) != 0) { - name = lua_tostring(L, -2); - if (lua_istartswith(name, p)) { - lua_readline_addcompletion(c, strdup(name)); - } - lua_pop(L, 1); - } - lua_pop(L, 1); -} - -static char *lua_readline_hint(const char *p, const char **ansi1, const char **ansi2) { - char *h = 0; - linenoiseCompletions c = {0}; - lua_readline_completions(p, &c); - if (c.len == 1) h = strdup(c.cvec[0] + strlen(p)); - linenoiseFreeCompletions(&c); - return h; -} - -static void lua_initreadline(lua_State *L) { - histpath = xasprintf("%s/.%s_history", _gc(xhomedir()), LUA_PROGNAME); - linenoiseSetCompletionCallback(lua_readline_completions); - linenoiseSetHintsCallback(lua_readline_hint); - linenoiseSetFreeHintsCallback(free); -} - -static int lua_readline(lua_State *L, char **b, const char *prompt) { - globalL = L; - linenoiseHistoryLoad(histpath); - return !!(*b = linenoise(prompt)); -} - -static void lua_saveline(lua_State *L, const char *line) { - linenoiseHistoryLoad(histpath); - linenoiseHistoryAdd(line); - linenoiseHistorySave(histpath); -} - -static void lua_freeline (lua_State *L, char *b) { - free(b); -} - -/* -** Return the string to be used as a prompt by the interpreter. Leave -** the string (or nil, if using the default value) on the stack, to keep -** it anchored. -*/ -static const char *get_prompt (lua_State *L, int firstline) { - if (lua_getglobal(L, firstline ? "_PROMPT" : "_PROMPT2") == LUA_TNIL) - return (firstline ? LUA_PROMPT : LUA_PROMPT2); /* use the default */ - else { /* apply 'tostring' over the value */ - const char *p = luaL_tolstring(L, -1, NULL); - lua_remove(L, -2); /* remove original value */ - return p; - } -} - -/* mark in error messages for incomplete statements */ -#define EOFMARK "" -#define marklen (sizeof(EOFMARK)/sizeof(char) - 1) - - -/* -** Check whether 'status' signals a syntax error and the error -** message at the top of the stack ends with the above mark for -** incomplete statements. -*/ -static int incomplete (lua_State *L, int status) { - if (status == LUA_ERRSYNTAX) { - size_t lmsg; - const char *msg = lua_tolstring(L, -1, &lmsg); - if (lmsg >= marklen && strcmp(msg + lmsg - marklen, EOFMARK) == 0) { - lua_pop(L, 1); - return 1; - } - } - return 0; /* else... */ -} - -/* -** Prompt the user, read a line, and push it into the Lua stack. -*/ -static int pushline (lua_State *L, int firstline) { - char buffer[LUA_MAXINPUT]; - char *b = buffer; - size_t l; - const char *prmt = get_prompt(L, firstline); - int readstatus = lua_readline(L, &b, prmt); - if (readstatus == 0) - return 0; /* no input (prompt will be popped by caller) */ - lua_pop(L, 1); /* remove prompt */ - l = strlen(b); - if (l > 0 && b[l-1] == '\n') /* line ends with newline? */ - b[--l] = '\0'; /* remove it */ - if (firstline && b[0] == '=') /* for compatibility with 5.2, ... */ - lua_pushfstring(L, "return %s", b + 1); /* change '=' to 'return' */ - else - lua_pushlstring(L, b, l); - lua_freeline(L, b); - return 1; -} - - -/* -** Try to compile line on the stack as 'return ;'; on return, stack -** has either compiled chunk or original line (if compilation failed). -*/ -static int addreturn (lua_State *L) { - const char *line = lua_tostring(L, -1); /* original line */ - const char *retline = lua_pushfstring(L, "return %s;", line); - int status = luaL_loadbuffer(L, retline, strlen(retline), "=stdin"); - if (status == LUA_OK) { - lua_remove(L, -2); /* remove modified line */ - if (line[0] != '\0') /* non empty? */ - lua_saveline(L, line); /* keep history */ - } - else - lua_pop(L, 2); /* pop result from 'luaL_loadbuffer' and modified line */ - return status; -} - - -/* -** Read multiple lines until a complete Lua statement -*/ -static int multiline (lua_State *L) { - for (;;) { /* repeat until gets a complete statement */ - size_t len; - const char *line = lua_tolstring(L, 1, &len); /* get what it has */ - int status = luaL_loadbuffer(L, line, len, "=stdin"); /* try it */ - if (!incomplete(L, status) || !pushline(L, 0)) { - lua_saveline(L, line); /* keep history */ - return status; /* cannot or should not try to add continuation line */ - } - lua_pushliteral(L, "\n"); /* add newline... */ - lua_insert(L, -2); /* ...between the two lines */ - lua_concat(L, 3); /* join them */ - } -} - - -/* -** Read a line and try to load (compile) it first as an expression (by -** adding "return " in front of it) and second as a statement. Return -** the final status of load/call with the resulting function (if any) -** in the top of the stack. -*/ -static int loadline (lua_State *L) { - int status; - lua_settop(L, 0); - if (!pushline(L, 1)) - return -1; /* no input */ - if ((status = addreturn(L)) != LUA_OK) /* 'return ...' did not work? */ - status = multiline(L); /* try as command, maybe with continuation lines */ - lua_remove(L, 1); /* remove line from the stack */ - lua_assert(lua_gettop(L) == 1); - return status; -} - - -/* -** Prints (calling the Lua 'print' function) any values on the stack -*/ -static void l_print (lua_State *L) { - int n = lua_gettop(L); - if (n > 0) { /* any result to be printed? */ - luaL_checkstack(L, LUA_MINSTACK, "too many results to print"); - lua_getglobal(L, "print"); - lua_insert(L, 1); - if (lua_pcall(L, n, 0, 0) != LUA_OK) - l_message(progname, lua_pushfstring(L, "error calling 'print' (%s)", - lua_tostring(L, -1))); - } -} - - /* ** Do the REPL: repeatedly read (load) a line, evaluate (call) it, and ** print any results. @@ -591,12 +275,15 @@ static void doREPL (lua_State *L) { int status; const char *oldprogname = progname; progname = NULL; /* no 'progname' on errors in interactive mode */ - lua_initreadline(L); - while ((status = loadline(L)) != -1) { + lua_initrepl(LUA_PROGNAME); + while ((status = lua_loadline(L)) != -1) { if (status == LUA_OK) - status = docall(L, 0, LUA_MULTRET); - if (status == LUA_OK) l_print(L); - else report(L, status); + status = lua_runchunk(L, 0, LUA_MULTRET); + if (status == LUA_OK) { + lua_l_print(L); + } else { + lua_report(L, status); + } } lua_settop(L, 0); /* clear stack */ lua_writeline(); @@ -654,13 +341,15 @@ static int pmain (lua_State *L) { int main (int argc, char **argv) { - ShowCrashReports(); int status, result; lua_State *L; + if (!IsModeDbg()) { + ShowCrashReports(); + } /* if (IsModeDbg()) ShowCrashReports(); */ L = luaL_newstate(); /* create state */ if (L == NULL) { - l_message(argv[0], "cannot create state: not enough memory"); + lua_l_message(argv[0], "cannot create state: not enough memory"); return EXIT_FAILURE; } lua_pushcfunction(L, &pmain); /* to call 'pmain' in protected mode */ @@ -668,7 +357,10 @@ int main (int argc, char **argv) { lua_pushlightuserdata(L, argv); /* 2nd argument */ status = lua_pcall(L, 2, 1, 0); /* do the call */ result = lua_toboolean(L, -1); /* get result */ - report(L, status); + lua_report(L, status); lua_close(L); + if (IsModeDbg()) { + CheckForMemoryLeaks(); + } return (result && status == LUA_OK) ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/tool/build/strace.c b/tool/build/strace.c index ba11e630a..f92b4c10c 100644 --- a/tool/build/strace.c +++ b/tool/build/strace.c @@ -31,6 +31,7 @@ #include "libc/fmt/fmt.h" #include "libc/fmt/itoa.h" #include "libc/intrin/kprintf.h" +#include "libc/intrin/nomultics.internal.h" #include "libc/log/check.h" #include "libc/log/log.h" #include "libc/mem/mem.h" @@ -66,9 +67,6 @@ #define PIPE 11 #define OCTAL 0x80 -extern bool __replmode; -extern bool __nomultics; - static const long __NR_brk = 12; static const long __NR_sigreturn = 15; diff --git a/tool/net/net.mk b/tool/net/net.mk index 46342feaf..d608bf557 100644 --- a/tool/net/net.mk +++ b/tool/net/net.mk @@ -54,16 +54,17 @@ TOOL_NET_DIRECTDEPS = \ LIBC_ZIPOS \ NET_HTTP \ NET_HTTPS \ - TOOL_BUILD_LIB \ + THIRD_PARTY_ARGON2 \ THIRD_PARTY_GDTOA \ THIRD_PARTY_GETOPT \ + THIRD_PARTY_LINENOISE \ THIRD_PARTY_LUA \ + THIRD_PARTY_MAXMIND \ THIRD_PARTY_MBEDTLS \ THIRD_PARTY_REGEX \ - THIRD_PARTY_MAXMIND \ THIRD_PARTY_SQLITE3 \ THIRD_PARTY_ZLIB \ - THIRD_PARTY_ARGON2 \ + TOOL_BUILD_LIB \ TOOL_DECODE_LIB TOOL_NET_DEPS := \ diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 188e2547a..6feeec8f5 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -37,6 +37,7 @@ #include "libc/errno.h" #include "libc/fmt/conv.h" #include "libc/fmt/itoa.h" +#include "libc/intrin/nomultics.internal.h" #include "libc/log/backtrace.internal.h" #include "libc/log/check.h" #include "libc/log/log.h" @@ -92,6 +93,7 @@ #include "libc/sysv/consts/sol.h" #include "libc/sysv/consts/tcp.h" #include "libc/sysv/consts/w.h" +#include "libc/sysv/errfuns.h" #include "libc/testlib/testlib.h" #include "libc/time/time.h" #include "libc/x/x.h" @@ -102,8 +104,10 @@ #include "net/http/url.h" #include "net/https/https.h" #include "third_party/getopt/getopt.h" +#include "third_party/linenoise/linenoise.h" #include "third_party/lua/cosmo.h" #include "third_party/lua/lauxlib.h" +#include "third_party/lua/lrepl.h" #include "third_party/lua/ltests.h" #include "third_party/lua/lua.h" #include "third_party/lua/luaconf.h" @@ -360,6 +364,7 @@ static bool hascontenttype; static bool sslclientverify; static bool connectionclose; static bool hasonworkerstop; +static bool isexitingworker; static bool hasonworkerstart; static bool leakcrashreports; static bool hasonhttprequest; @@ -408,6 +413,7 @@ static const char *brand; static char gzip_footer[8]; static const char *pidpath; static const char *logpath; +static const char *histpath; static struct pollfd *polls; static struct Strings loops; static size_t payloadlength; @@ -453,7 +459,6 @@ static struct TlsBio g_bio; static char slashpath[PATH_MAX]; static struct DeflateGenerator dg; -static wontreturn void ExitWorker(void); static char *Route(const char *, size_t, const char *, size_t); static char *RouteHost(const char *, size_t, const char *, size_t); static char *RoutePath(const char *, size_t); @@ -6563,8 +6568,18 @@ static void CloseServerFds(void) { } } -static void HandleConnection(size_t i) { - int pid; +static int ExitWorker(void) { + if (!IsModeDbg()) { + _Exit(0); + } else { + isexitingworker = true; + return eintr(); + } +} + +// returns 0 otherwise -1 if worker needs to unwind stack and exit +static int HandleConnection(size_t i) { + int pid, rc = 0; clientaddrsize = sizeof(clientaddr); if ((client = accept4(servers.p[i].fd, &clientaddr, &clientaddrsize, SOCK_CLOEXEC)) != -1) { @@ -6572,7 +6587,7 @@ static void HandleConnection(size_t i) { messageshandled = 0; if (hasonclientconnection && LuaOnClientConnection()) { close(client); - return; + return 0; } if (uniprocess) { pid = -1; @@ -6591,7 +6606,7 @@ static void HandleConnection(size_t i) { break; case -1: HandleForkFailure(); - return; + return 0; default: ++shared->workers; close(client); @@ -6599,7 +6614,7 @@ static void HandleConnection(size_t i) { if (hasonprocesscreate) { LuaOnProcessCreate(pid); } - return; + return 0; } } if (!pid && !IsWindows()) { @@ -6613,7 +6628,7 @@ static void HandleConnection(size_t i) { if (hasonworkerstop) { CallSimpleHook("OnWorkerStop"); } - ExitWorker(); + rc = ExitWorker(); } else { close(client); oldin.p = 0; @@ -6631,68 +6646,87 @@ static void HandleConnection(size_t i) { mbedtls_ssl_session_reset(&ssl); } #endif - CollectGarbage(); } - } else if (errno == EINTR || errno == EAGAIN) { - LockInc(&shared->c.acceptinterrupts); - } else if (errno == ENFILE) { - LockInc(&shared->c.enfiles); - WARNF("(srvr) too many open files"); - meltdown = true; - } else if (errno == EMFILE) { - LockInc(&shared->c.emfiles); - WARNF("(srvr) ran out of open file quota"); - meltdown = true; - } else if (errno == ENOMEM) { - LockInc(&shared->c.enomems); - WARNF("(srvr) ran out of memory"); - meltdown = true; - } else if (errno == ENOBUFS) { - LockInc(&shared->c.enobufs); - WARNF("(srvr) ran out of buffer"); - meltdown = true; - } else if (errno == ENONET) { - LockInc(&shared->c.enonets); - WARNF("(srvr) %s network gone", DescribeServer()); - polls[i].fd = -polls[i].fd; - } else if (errno == ENETDOWN) { - LockInc(&shared->c.enetdowns); - WARNF("(srvr) %s network down", DescribeServer()); - polls[i].fd = -polls[i].fd; - } else if (errno == ECONNABORTED) { - LockInc(&shared->c.acceptresets); - WARNF("(srvr) %s connection reset before accept"); - } else if (errno == ENETUNREACH || errno == EHOSTUNREACH || - errno == EOPNOTSUPP || errno == ENOPROTOOPT || errno == EPROTO) { - LockInc(&shared->c.accepterrors); - WARNF("(srvr) %s ephemeral accept error: %m", DescribeServer()); + CollectGarbage(); } else { - DIEF("(srvr) %s accept error: %m", DescribeServer()); + if (errno == EINTR || errno == EAGAIN) { + LockInc(&shared->c.acceptinterrupts); + } else if (errno == ENFILE) { + LockInc(&shared->c.enfiles); + WARNF("(srvr) too many open files"); + meltdown = true; + } else if (errno == EMFILE) { + LockInc(&shared->c.emfiles); + WARNF("(srvr) ran out of open file quota"); + meltdown = true; + } else if (errno == ENOMEM) { + LockInc(&shared->c.enomems); + WARNF("(srvr) ran out of memory"); + meltdown = true; + } else if (errno == ENOBUFS) { + LockInc(&shared->c.enobufs); + WARNF("(srvr) ran out of buffer"); + meltdown = true; + } else if (errno == ENONET) { + LockInc(&shared->c.enonets); + WARNF("(srvr) %s network gone", DescribeServer()); + polls[i].fd = -polls[i].fd; + } else if (errno == ENETDOWN) { + LockInc(&shared->c.enetdowns); + WARNF("(srvr) %s network down", DescribeServer()); + polls[i].fd = -polls[i].fd; + } else if (errno == ECONNABORTED) { + LockInc(&shared->c.acceptresets); + WARNF("(srvr) %s connection reset before accept"); + } else if (errno == ENETUNREACH || errno == EHOSTUNREACH || + errno == EOPNOTSUPP || errno == ENOPROTOOPT || errno == EPROTO) { + LockInc(&shared->c.accepterrors); + WARNF("(srvr) %s ephemeral accept error: %m", DescribeServer()); + } else { + DIEF("(srvr) %s accept error: %m", DescribeServer()); + } + errno = 0; } - errno = 0; + return rc; } -static void HandlePoll(void) { +// returns 2 if we should stay in the redbean event loop +// returns 1 if poll() says stdin has user input available +// returns 0 if poll() timed out after ms +// returns -1 if worker is unwinding exit +static int HandlePoll(int ms) { size_t i; - if (poll(polls, servers.n, HEARTBEAT) != -1) { + int nfds; + if ((nfds = poll(polls, 1 + servers.n, ms)) != -1) { for (i = 0; i < servers.n; ++i) { - if (polls[i].revents) { + if (polls[1 + i].revents) { serveraddr = &servers.p[i].addr; ishandlingconnection = true; - HandleConnection(i); + if (HandleConnection(i) == -1) return -1; ishandlingconnection = false; } } - } else if (errno == EINTR || errno == EAGAIN) { - LockInc(&shared->c.pollinterrupts); - } else if (errno == ENOMEM) { - LockInc(&shared->c.enomems); - WARNF("(srvr) %s ran out of memory"); - meltdown = true; + // are we polling stdin for the repl? + if (polls[0].fd >= 0) { + if (polls[0].revents) { + return 1; // user entered a keystroke + } else if (!nfds) { + return 0; // let linenoise know it timed out + } + } } else { - DIEF("(srvr) poll error: %m"); + if (errno == EINTR || errno == EAGAIN) { + LockInc(&shared->c.pollinterrupts); + } else if (errno == ENOMEM) { + LockInc(&shared->c.enomems); + WARNF("(srvr) %s ran out of memory"); + meltdown = true; + } else { + DIEF("(srvr) poll error: %m"); + } + errno = 0; } - errno = 0; + return 2; } static void RestoreApe(void) { @@ -6769,18 +6803,21 @@ static void Listen(void) { } } servers.n = n; - polls = malloc(n * sizeof(*polls)); + polls = malloc((1 + n) * sizeof(*polls)); + polls[0].fd = -1; + polls[0].events = POLLIN; + polls[0].revents = 0; for (i = 0; i < n; ++i) { - polls[i].fd = servers.p[i].fd; - polls[i].events = POLLIN; - polls[i].revents = 0; + polls[1 + i].fd = servers.p[i].fd; + polls[1 + i].events = POLLIN; + polls[1 + i].revents = 0; } } static void HandleShutdown(void) { CloseServerFds(); INFOF("(srvr) received %s", strsignal(shutdownsig)); - if (shutdownsig == SIGTERM) { + if (shutdownsig != SIGINT && shutdownsig != SIGQUIT) { if (!killed) terminated = false; INFOF("(srvr) killing process group"); KillGroup(); @@ -6788,9 +6825,14 @@ static void HandleShutdown(void) { WaitAll(); } -static void HandleEvents(void) { +// this function coroutines with linenoise +static int EventLoop(int fd, int ms) { + int rc; long double t; + rc = -1; + polls[0].fd = 0; while (!terminated) { + errno = 0; if (zombied) { ReapZombies(); } else if (invalidated) { @@ -6799,13 +6841,42 @@ static void HandleEvents(void) { } else if (meltdown) { EnterMeltdownMode(); meltdown = false; - } else if ((t = nowl()) - lastheartbeat > .5) { + } else if ((t = nowl()) - lastheartbeat > HEARTBEAT / 1000.) { lastheartbeat = t; HandleHeartbeat(); - } else { - HandlePoll(); + } else if ((rc = HandlePoll(ms)) != 2) { + break; // return control to linenoise } } + polls[0].fd = -1; + return rc; +} + +static void ReplEventLoop(void) { + int status; + lua_State *L = GL; + __nomultics = 2; + __replmode = true; + lua_initrepl("redbean"); + linenoiseSetPollCallback(EventLoop); + while ((status = lua_loadline(L)) != -1) { + if (status == LUA_OK) { + status = lua_runchunk(L, 0, LUA_MULTRET); + } + if (status == LUA_OK) { + lua_l_print(L); + } else { + lua_report(L, status); + } + } + if (!terminated && !isexitingworker) { + OnTerm(SIGHUP); // eof event + } + lua_settop(L, 0); // clear stack + lua_writeline(); + __replmode = false; + __nomultics = 0; + polls[0].fd = -1; } static void SigInit(void) { @@ -6917,16 +6988,6 @@ static void MemDestroy(void) { Free(&polls); } -static wontreturn void ExitWorker(void) { - if (IsModeDbg()) { - LuaDestroy(); - TlsDestroy(); - MemDestroy(); - CheckForMemoryLeaks(); - } - _Exit(0); -} - static void GetOpts(int argc, char *argv[]) { int opt; bool storeasset = false; @@ -7032,9 +7093,19 @@ void RedBean(int argc, char *argv[]) { inbuf = inbuf_actual; isinitialized = true; CallSimpleHookIfDefined("OnServerStart"); - HandleEvents(); - HandleShutdown(); - CallSimpleHookIfDefined("OnServerStop"); +#ifdef STATIC + EventLoop(); +#else + if (isatty(0)) { + ReplEventLoop(); + } else { + EventLoop(-1, HEARTBEAT); + } +#endif + if (!isexitingworker) { + HandleShutdown(); + CallSimpleHookIfDefined("OnServerStop"); + } if (!IsTiny()) { LuaDestroy(); TlsDestroy();