Rewrite Windows console input handling

This change removes our use of ENABLE_VIRTUAL_TERMINAL_INPUT (which
isn't very good) in favor of having read() translate Windows Console
input events to ANSI/XTERM sequences by hand. This makes it possible to
capture important keystrokes (e.g. ctrl-space) that weren't possible
before. Most importantly this change also removes the stdin/sigwinch
worker threads, which never really worked that well. Interactive TTY
sessions will now work reliably when a Cosmo process spawns or forks
another Cosmo process, e.g. unbourne.com launching emacs.com.
This commit is contained in:
Justine Tunney 2023-09-19 11:42:38 -07:00
parent ececec4c94
commit d6c2830850
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
27 changed files with 635 additions and 464 deletions

View file

@ -28,8 +28,6 @@
#include "libc/sysv/consts/termios.h"
#include "libc/x/xsigaction.h"
__static_yoink("WinMainStdin");
#define CTRL(C) ((C) ^ 0b01000000)
#define WRITE(FD, SLIT) write(FD, SLIT, strlen(SLIT))
#define ENABLE_SAFE_PASTE "\e[?2004h"
@ -69,7 +67,7 @@ int rawmode(void) {
memcpy(&t, &oldterm, sizeof(t));
t.c_cc[VMIN] = 1;
t.c_cc[VTIME] = 1;
t.c_cc[VTIME] = 0;
// emacs does the following to remap ctrl-c to ctrl-g in termios
// t.c_cc[VINTR] = CTRL('G');

View file

@ -0,0 +1,14 @@
#ifndef COSMOPOLITAN_LIBC_CALLS_CONSOLE_INTERNAL_H_
#define COSMOPOLITAN_LIBC_CALLS_CONSOLE_INTERNAL_H_
#include "libc/calls/struct/fd.internal.h"
#include "libc/nt/struct/inputrecord.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
int CountConsoleInputBytes(int64_t);
int ConvertConsoleInputToAnsi(const struct NtInputRecord *, char[hasatleast 32],
uint16_t *, struct Fd *);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_CALLS_CONSOLE_INTERNAL_H_ */

View file

@ -16,17 +16,41 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/intrin/atomic.h"
#include "libc/nt/synchronization.h"
#include "libc/calls/console.internal.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/errno.h"
#include "libc/macros.internal.h"
#include "libc/nt/console.h"
#include "libc/nt/struct/inputrecord.h"
#ifdef __x86_64__
textwindows int64_t __resolve_stdin_handle(int64_t handle) {
if (handle == g_fds.stdin.handle) {
if (g_fds.stdin.inisem) {
ReleaseSemaphore(g_fds.stdin.inisem, 1, 0);
int CountConsoleInputBytes(int64_t handle) {
char buf[32];
int rc, e = errno;
uint16_t utf16hs = 0;
uint32_t i, n, count;
struct NtInputRecord records[64];
if (PeekConsoleInput(handle, records, ARRAYLEN(records), &n)) {
for (rc = i = 0; i < n; ++i) {
count = ConvertConsoleInputToAnsi(records + i, buf, &utf16hs, 0);
if (count == -1) {
unassert(errno == ENODATA);
if (!rc) {
rc = -1;
} else {
errno = e;
}
break;
}
rc += count;
}
handle = g_fds.stdin.reader;
} else {
rc = __winerr();
}
return handle;
return rc;
}
#endif /* __x86_64__ */

View file

@ -17,6 +17,7 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/console.internal.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
@ -31,6 +32,7 @@
#include "libc/macros.internal.h"
#include "libc/mem/alloca.h"
#include "libc/mem/mem.h"
#include "libc/nt/console.h"
#include "libc/nt/enum/filetype.h"
#include "libc/nt/errors.h"
#include "libc/nt/files.h"
@ -54,10 +56,6 @@
#include "libc/sysv/consts/termios.h"
#include "libc/sysv/errfuns.h"
#ifdef __x86_64__
__static_yoink("WinMainStdin");
#endif
/* Maximum number of unicast addresses handled for each interface */
#define MAX_UNICAST_ADDR 32
#define MAX_NAME_CLASH ((int)('z' - 'a')) /* Allow a..z */
@ -93,13 +91,14 @@ static int ioctl_default(int fd, unsigned long request, void *arg) {
}
static int ioctl_fionread(int fd, uint32_t *arg) {
int rc;
uint32_t cm;
int64_t handle;
uint32_t avail;
int rc, e = errno;
if (!IsWindows()) {
return sys_ioctl(fd, FIONREAD, arg);
} else if (__isfdopen(fd)) {
handle = __resolve_stdin_handle(g_fds.p[fd].handle);
handle = g_fds.p[fd].handle;
if (g_fds.p[fd].kind == kFdSocket) {
if ((rc = _weaken(__sys_ioctlsocket_nt)(handle, FIONREAD, arg)) != -1) {
return rc;
@ -113,6 +112,13 @@ static int ioctl_fionread(int fd, uint32_t *arg) {
} else {
return __winerr();
}
} else if (GetConsoleMode(handle, &cm)) {
avail = CountConsoleInputBytes(handle);
if (avail == -1u && errno == ENODATA) {
errno = e;
avail = 0;
}
return avail;
} else {
return eopnotsupp();
}

View file

@ -17,6 +17,7 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/calls/console.internal.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
@ -26,6 +27,7 @@
#include "libc/intrin/strace.internal.h"
#include "libc/macros.internal.h"
#include "libc/mem/mem.h"
#include "libc/nt/console.h"
#include "libc/nt/enum/filetype.h"
#include "libc/nt/errors.h"
#include "libc/nt/files.h"
@ -47,8 +49,6 @@
#ifdef __x86_64__
__static_yoink("WinMainStdin");
/*
* Polls on the New Technology.
*
@ -59,8 +59,8 @@ __static_yoink("WinMainStdin");
textwindows int sys_poll_nt(struct pollfd *fds, uint64_t nfds, uint64_t *ms,
const sigset_t *sigmask) {
bool ok;
uint32_t avail;
sigset_t oldmask;
uint32_t avail, cm;
struct sys_pollfd_nt pipefds[8];
struct sys_pollfd_nt sockfds[64];
int pipeindices[ARRAYLEN(pipefds)];
@ -98,7 +98,7 @@ textwindows int sys_poll_nt(struct pollfd *fds, uint64_t nfds, uint64_t *ms,
}
} else if (pn < ARRAYLEN(pipefds)) {
pipeindices[pn] = i;
pipefds[pn].handle = __resolve_stdin_handle(g_fds.p[fds[i].fd].handle);
pipefds[pn].handle = g_fds.p[fds[i].fd].handle;
pipefds[pn].events = 0;
pipefds[pn].revents = 0;
switch (g_fds.p[fds[i].fd].flags & O_ACCMODE) {
@ -151,6 +151,19 @@ textwindows int sys_poll_nt(struct pollfd *fds, uint64_t nfds, uint64_t *ms,
} else {
pipefds[i].revents |= POLLERR;
}
} else if (GetConsoleMode(pipefds[i].handle, &cm)) {
int e = errno;
avail = CountConsoleInputBytes(pipefds[i].handle);
if (avail > 0) {
pipefds[i].revents |= POLLIN;
} else if (avail == -1u) {
if (errno == ENODATA) {
pipefds[i].revents |= POLLIN;
} else {
pipefds[i].revents |= POLLERR;
}
errno = e;
}
} else {
// we have no way of polling if a non-socket is readable yet
// therefore we assume that if it can happen it shall happen

View file

@ -17,32 +17,39 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/calls/console.internal.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/calls/struct/iovec.h"
#include "libc/calls/struct/iovec.internal.h"
#include "libc/calls/struct/timespec.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/calls/wincrash.internal.h"
#include "libc/errno.h"
#include "libc/intrin/kprintf.h"
#include "libc/fmt/itoa.h"
#include "libc/intrin/nomultics.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/macros.internal.h"
#include "libc/nt/console.h"
#include "libc/nt/enum/filetype.h"
#include "libc/nt/enum/vk.h"
#include "libc/nt/enum/wait.h"
#include "libc/nt/errors.h"
#include "libc/nt/events.h"
#include "libc/nt/files.h"
#include "libc/nt/ipc.h"
#include "libc/nt/runtime.h"
#include "libc/nt/struct/inputrecord.h"
#include "libc/nt/struct/overlapped.h"
#include "libc/nt/synchronization.h"
#include "libc/nt/thread.h"
#include "libc/nt/thunk/msabi.h"
#include "libc/str/str.h"
#include "libc/str/utf16.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/consts/termios.h"
@ -50,33 +57,385 @@
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/tls.h"
__static_yoink("WinMainStdin");
#ifdef __x86_64__
__msabi extern typeof(CloseHandle) *const __imp_CloseHandle;
static const struct {
uint16_t vk;
uint32_t normal_str;
uint32_t shift_str;
uint32_t ctrl_str;
uint32_t shift_ctrl_str;
} kVirtualKey[] = {
#define SW(s) W4(s "\0\0")
#define W4(s) (s[3] + 0u) << 24 | s[2] << 16 | s[1] << 8 | s[0]
#define VK(vk, normal_str, shift_str, ctrl_str, shift_ctrl_str) \
{ vk, SW(normal_str), SW(shift_str), SW(ctrl_str), SW(shift_ctrl_str) }
VK(kNtVkInsert, "2~", "2;2~", "2;5~", "2;6~"),
VK(kNtVkEnd, "4~", "4;2~", "4;5~", "4;6~"),
VK(kNtVkDown, "B", "1;2B", "1;5B", "1;6B"),
VK(kNtVkNext, "6~", "6;2~", "6;5~", "6;6~"),
VK(kNtVkLeft, "D", "1;2D", "1;5D", "1;6D"),
VK(kNtVkClear, "G", "1;2G", "1;5G", "1;6G"),
VK(kNtVkRight, "C", "1;2C", "1;5C", "1;6C"),
VK(kNtVkUp, "A", "1;2A", "1;5A", "1;6A"),
VK(kNtVkHome, "1~", "1;2~", "1;5~", "1;6~"),
VK(kNtVkPrior, "5~", "5;2~", "5;5~", "5;6~"),
VK(kNtVkDelete, "3~", "3;2~", "3;5~", "3;6~"),
VK(kNtVkNumpad0, "2~", "2;2~", "2;5~", "2;6~"),
VK(kNtVkNumpad1, "4~", "4;2~", "4;5~", "4;6~"),
VK(kNtVkNumpad2, "B", "1;2B", "1;5B", "1;6B"),
VK(kNtVkNumpad3, "6~", "6;2~", "6;5~", "6;6~"),
VK(kNtVkNumpad4, "D", "1;2D", "1;5D", "1;6D"),
VK(kNtVkNumpad5, "G", "1;2G", "1;5G", "1;6G"),
VK(kNtVkNumpad6, "C", "1;2C", "1;5C", "1;6C"),
VK(kNtVkNumpad7, "A", "1;2A", "1;5A", "1;6A"),
VK(kNtVkNumpad8, "1~", "1;2~", "1;5~", "1;6~"),
VK(kNtVkNumpad9, "5~", "5;2~", "5;5~", "5;6~"),
VK(kNtVkDecimal, "3~", "3;2~", "3;5~", "3;6~"),
VK(kNtVkF1, "[A", "23~", "11^", "23^"),
VK(kNtVkF2, "[B", "24~", "12^", "24^"),
VK(kNtVkF3, "[C", "25~", "13^", "25^"),
VK(kNtVkF4, "[D", "26~", "14^", "26^"),
VK(kNtVkF5, "[E", "28~", "15^", "28^"),
VK(kNtVkF6, "17~", "29~", "17^", "29^"),
VK(kNtVkF7, "18~", "31~", "18^", "31^"),
VK(kNtVkF8, "19~", "32~", "19^", "32^"),
VK(kNtVkF9, "20~", "33~", "20^", "33^"),
VK(kNtVkF10, "21~", "34~", "21^", "34^"),
VK(kNtVkF11, "23~", "23$", "23^", "23@"),
VK(kNtVkF12, "24~", "24$", "24^", "24@"),
#undef VK
#undef W4
#undef SW
};
static textwindows int ProcessSignal(int sig, struct Fd *f) {
if (f) {
if (_weaken(__sig_raise)) {
pthread_mutex_unlock(&f->lock);
_weaken(__sig_raise)(sig, SI_KERNEL);
pthread_mutex_lock(&f->lock);
if (!(__sighandflags[sig] & SA_RESTART)) {
return eintr();
}
} else if (sig != SIGWINCH) {
TerminateThisProcess(sig);
}
}
return 0;
}
static textwindows uint32_t GetVirtualKey(uint16_t vk, bool shift, bool ctrl) {
for (int i = 0; i < ARRAYLEN(kVirtualKey); ++i) {
if (kVirtualKey[i].vk == vk) {
if (shift && ctrl) {
return kVirtualKey[i].shift_ctrl_str;
} else if (shift) {
return kVirtualKey[i].shift_str;
} else if (ctrl) {
return kVirtualKey[i].ctrl_str;
} else {
return kVirtualKey[i].normal_str;
}
}
}
return 0;
}
// Manual CMD.EXE echoing for when !ICANON && ECHO is the case.
static textwindows void EchoTerminalInput(struct Fd *f, char *p, size_t n) {
int64_t hOutput;
if (f->kind == kFdConsole) {
hOutput = f->extra;
} else {
hOutput = g_fds.p[1].handle;
}
if (__ttymagic & kFdTtyEchoRaw) {
WriteFile(hOutput, p, n, 0, 0);
} else {
size_t i;
for (i = 0; i < n; ++i) {
if (isascii(p[i]) && iscntrl(p[i]) && p[i] != '\n' && p[i] != '\t') {
char ctl[2];
ctl[0] = '^';
ctl[1] = p[i] ^ 0100;
WriteFile(hOutput, ctl, 2, 0, 0);
} else {
WriteFile(hOutput, p + i, 1, 0, 0);
}
}
}
}
static textwindows int ProcessKeyEvent(const struct NtInputRecord *r, char *p,
uint16_t *utf16hs, struct Fd *f) {
uint16_t c = r->Event.KeyEvent.uChar.UnicodeChar;
uint16_t vk = r->Event.KeyEvent.wVirtualKeyCode;
uint16_t cks = r->Event.KeyEvent.dwControlKeyState;
// ignore keyup events
if (!r->Event.KeyEvent.bKeyDown && (!c || vk != kNtVkMenu)) {
return 0;
}
// ignore numpad being used to compose a character
if ((cks & kNtLeftAltPressed) && !(cks & kNtEnhancedKey) &&
(vk == kNtVkInsert || vk == kNtVkEnd || vk == kNtVkDown ||
vk == kNtVkNext || vk == kNtVkLeft || vk == kNtVkClear ||
vk == kNtVkRight || vk == kNtVkHome || vk == kNtVkUp ||
vk == kNtVkPrior || vk == kNtVkNumpad0 || vk == kNtVkNumpad1 ||
vk == kNtVkNumpad2 || vk == kNtVkNumpad3 || vk == kNtVkNumpad4 ||
vk == kNtVkNumpad5 || vk == kNtVkNumpad6 || vk == kNtVkNumpad7 ||
vk == kNtVkNumpad8 || vk == kNtVkNumpad9)) {
return 0;
}
int n = 0;
// process virtual keys
if (!c) {
uint32_t w;
w = GetVirtualKey(vk, !!(cks & kNtShiftPressed),
!!(cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed)));
if (!w) return 0;
p[n++] = 033;
if (cks & (kNtLeftAltPressed | kNtRightAltPressed)) {
p[n++] = 033;
}
p[n++] = '[';
do p[n++] = w;
while ((w >>= 8));
return n;
}
// translate utf-16 into utf-32
if (IsHighSurrogate(c)) {
*utf16hs = c;
return 0;
}
if (IsLowSurrogate(c)) {
c = MergeUtf16(*utf16hs, c);
}
// enter sends \r in a raw terminals
// make it a multics newline instead
if (c == '\r' && !(__ttymagic & kFdTtyNoCr2Nl)) {
c = '\n';
}
// microsoft doesn't encode ctrl-space (^@) as nul
// detecting it is also impossible w/ kNtEnableVirtualTerminalInput
if (c == ' ' && (cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed))) {
c = '\0';
}
// make it possible to distinguish ctrl-h (^H) from backspace (^?)
if (c == kNtVkBack) {
c = 0177;
}
// handle ctrl-c and ctrl-\, which tcsetattr() is able to remap
if (!(__ttymagic & kFdTtyNoIsigs)) {
if (c == __vintr && __vintr != _POSIX_VDISABLE) {
return ProcessSignal(SIGINT, f);
} else if (c == __vquit && __vquit != _POSIX_VDISABLE) {
return ProcessSignal(SIGQUIT, f);
}
}
// handle ctrl-d the end of file keystroke
if (c == __veof && __veof != _POSIX_VDISABLE) {
return enodata();
}
// insert esc prefix when alt is held
if ((cks & (kNtLeftAltPressed | kNtRightAltPressed)) &&
!(cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed)) &&
r->Event.KeyEvent.bKeyDown) {
p[n++] = 033;
}
// convert utf-32 to utf-8
uint64_t w = tpenc(c);
do p[n++] = w;
while ((w >>= 8));
return n;
}
// To use the tty mouse events feature:
// - write(1, "\e[?1000;1002;1015;1006h") to enable
// - write(1, "\e[?1000;1002;1015;1006l") to disable
// See o//examples/ttyinfo.com and o//tool/viz/life.com
static textwindows int ProcessMouseEvent(const struct NtInputRecord *r, char *b,
struct Fd *f) {
int e = 0;
char *p = b;
uint32_t currentbs = f ? f->mousebuttons : 0;
uint32_t ev = r->Event.MouseEvent.dwEventFlags;
uint32_t bs = r->Event.MouseEvent.dwButtonState;
ev &= kNtMouseMoved | kNtMouseWheeled;
bs &= kNtFromLeft1stButtonPressed | kNtRightmostButtonPressed;
if (ev & kNtMouseWheeled) {
// scroll wheel (unnatural mode)
if (!(r->Event.MouseEvent.dwControlKeyState &
(kNtShiftPressed | kNtLeftCtrlPressed | kNtRightCtrlPressed |
kNtLeftAltPressed | kNtRightAltPressed))) {
bool isup = ((int)r->Event.MouseEvent.dwButtonState >> 16) > 0;
if (__ttymagic & kFdTtyXtMouse) {
e = isup ? 80 : 81;
goto OutputXtermMouseEvent;
} else {
// we disable mouse highlighting when the tty is put in raw mode
// to mouse wheel events with widely understood vt100 arrow keys
*p++ = 033;
*p++ = '[';
if (isup) {
*p++ = 'A'; // \e[A up
} else {
*p++ = 'B'; // \e[B down
}
}
}
} else if ((bs || currentbs) && (__ttymagic & kFdTtyXtMouse)) {
if (bs && (ev & kNtMouseMoved) && currentbs) {
e |= 32; // dragging
}
if ((bs | currentbs) & kNtRightmostButtonPressed) {
e |= 2; // right
}
OutputXtermMouseEvent:
*p++ = 033;
*p++ = '[';
*p++ = '<';
p = FormatInt32(p, e);
*p++ = ';';
p = FormatInt32(p, r->Event.MouseEvent.dwMousePosition.X + 1);
*p++ = ';';
p = FormatInt32(p, r->Event.MouseEvent.dwMousePosition.Y + 1);
if (!bs && currentbs) {
*p++ = 'm'; // up
} else {
*p++ = 'M'; // down
}
if (f) {
f->mousebuttons = bs;
}
}
return p - b;
}
textwindows int ConvertConsoleInputToAnsi(const struct NtInputRecord *r,
char p[hasatleast 32],
uint16_t *utf16hs, struct Fd *f) {
switch (r->EventType) {
case kNtKeyEvent:
return ProcessKeyEvent(r, p, utf16hs, f);
case kNtMouseEvent:
return ProcessMouseEvent(r, p, f);
case kNtWindowBufferSizeEvent:
return ProcessSignal(SIGWINCH, f);
default:
return 0;
}
}
static textwindows ssize_t ReadFromWindowsConsole(struct Fd *f, void *data,
size_t size) {
ssize_t rc;
int e = errno;
uint16_t utf16hs = 0;
pthread_mutex_lock(&f->lock);
for (;;) {
if (f->eoftty) {
rc = 0;
break;
}
uint32_t got = MIN(size, f->buflen);
uint32_t remain = f->buflen - got;
if (got) memcpy(data, f->buf, got);
if (remain) memmove(f->buf, f->buf + got, remain);
f->buflen = remain;
if (got) {
rc = got;
break;
}
uint32_t events_available;
if (!GetNumberOfConsoleInputEvents(f->handle, &events_available)) {
rc = __winerr();
break;
}
if (events_available) {
uint32_t n;
struct NtInputRecord r;
if (!ReadConsoleInput(f->handle, &r, 1, &n)) {
rc = __winerr();
break;
}
rc = ConvertConsoleInputToAnsi(&r, f->buf, &utf16hs, f);
if (rc == -1) {
if (errno == ENODATA) {
f->eoftty = true;
errno = e;
rc = 0;
}
break;
}
f->buflen = rc;
} else {
if (f->flags & O_NONBLOCK) {
rc = 0;
break;
}
uint32_t ms = __SIG_POLL_INTERVAL_MS;
if (__ttymagic & kFdTtyNoBlock) {
if (!__vtime) {
rc = 0;
break;
} else {
ms = __vtime * 100;
}
}
if ((rc = _check_interrupts(kSigOpRestartable))) {
break;
}
struct PosixThread *pt = _pthread_self();
pt->pt_flags |= PT_INSEMAPHORE;
WaitForSingleObject(pt->semaphore, ms);
pt->pt_flags &= ~PT_INSEMAPHORE;
if (pt->abort_errno == ECANCELED) {
rc = ecanceled();
break;
}
}
}
pthread_mutex_unlock(&f->lock);
if (rc > 0 && (__ttymagic & kFdTtyEchoing)) {
EchoTerminalInput(f, data, size);
}
return rc;
}
textwindows ssize_t sys_read_nt_impl(int fd, void *data, size_t size,
int64_t offset) {
// perform the read i/o operation
bool32 ok;
struct Fd *f;
uint32_t got;
int64_t handle;
uint32_t remain;
char *targetdata;
uint32_t targetsize;
struct PosixThread *pt;
f = g_fds.p + fd;
handle = __resolve_stdin_handle(f->handle);
handle = f->handle;
pt = _pthread_self();
pt->abort_errno = EAGAIN;
size = MIN(size, 0x7ffff000);
bool pwriting = offset != -1;
bool seekable = f->kind == kFdFile && GetFileType(handle) == kNtFileTypeDisk;
bool nonblock = !!(f->flags & O_NONBLOCK);
pt->abort_errno = EAGAIN;
if (pwriting && !seekable) {
return espipe();
@ -85,33 +444,9 @@ textwindows ssize_t sys_read_nt_impl(int fd, void *data, size_t size,
offset = 0;
}
uint32_t dwConsoleMode;
bool is_console_input =
g_fds.stdin.handle
? f->handle == g_fds.stdin.handle
: !seekable && (f->kind == kFdConsole ||
GetConsoleMode(handle, &dwConsoleMode));
StartOver:
size = MIN(size, 0x7ffff000);
// the caller might be reading a single byte at a time. but we need to
// be able to munge three bytes into just 1 e.g. "\342\220\200" → "\0"
if (size && f->buflen) {
ReturnDataFromBuffer:
got = MIN(size, f->buflen);
remain = f->buflen - got;
if (got) memcpy(data, f->buf, got);
if (remain) memmove(f->buf, f->buf + got, remain);
f->buflen = remain;
return got;
}
if (is_console_input && size && size < 3 && (__ttymagic & kFdTtyMunging)) {
targetdata = f->buf;
targetsize = sizeof(f->buf);
} else {
targetdata = data;
targetsize = size;
uint32_t cm;
if (!seekable && (f->kind == kFdConsole || GetConsoleMode(handle, &cm))) {
return ReadFromWindowsConsole(f, data, size);
}
if (!pwriting && seekable) {
@ -123,7 +458,7 @@ StartOver:
.Pointer = offset};
// the win32 manual says it's important to *not* put &got here
// since for overlapped i/o, we always use GetOverlappedResult
ok = ReadFile(handle, targetdata, targetsize, 0, &overlap);
ok = ReadFile(handle, data, size, 0, &overlap);
if (!ok && GetLastError() == kNtErrorIoPending) {
BlockingOperation:
if (!nonblock) {
@ -172,27 +507,6 @@ StartOver:
}
if (ok) {
if (is_console_input) {
if (__ttymagic & kFdTtyMunging) {
switch (_weaken(__munge_terminal_input)(targetdata, &got)) {
case DO_NOTHING:
break;
case DO_RESTART:
goto StartOver;
case DO_EINTR:
return eintr();
default:
__builtin_unreachable();
}
}
if (__ttymagic & kFdTtyEchoing) {
_weaken(__echo_terminal_input)(f, targetdata, got);
}
}
if (targetdata != data) {
f->buflen = got;
goto ReturnDataFromBuffer;
}
return got;
}

View file

@ -160,13 +160,12 @@ textwindows int __sig_raise(int sig, int sic) {
return (flags & SA_RESTART) ? 2 : 1;
}
textwindows void __sig_cancel(struct PosixThread *pt, unsigned flags) {
textwindows void __sig_cancel(struct PosixThread *pt) {
atomic_int *futex;
if (_weaken(WakeByAddressSingle) &&
(futex = atomic_load_explicit(&pt->pt_futex, memory_order_acquire))) {
_weaken(WakeByAddressSingle)(futex);
} else if (!(flags & SA_RESTART) && pt->iohandle > 0) {
pt->abort_errno = EINTR;
} else if (pt->iohandle > 0) {
if (!CancelIoEx(pt->iohandle, pt->ioverlap)) {
int err = GetLastError();
if (err != kNtErrorNotFound) {
@ -174,7 +173,6 @@ textwindows void __sig_cancel(struct PosixThread *pt, unsigned flags) {
}
}
} else if (pt->pt_flags & PT_INSEMAPHORE) {
pt->abort_errno = EINTR;
if (!ReleaseSemaphore(pt->semaphore, 1, 0)) {
STRACE("ReleaseSemaphore() failed w/ %d", GetLastError());
}
@ -259,8 +257,9 @@ static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
STRACE("SetThreadContext failed w/ %d", GetLastError());
return ESRCH;
}
ResumeThread(th); // doesn't actually resume if doing blocking i/o
__sig_cancel(pt, flags); // we can wake it up immediately by canceling it
ResumeThread(th); // doesn't actually resume if doing blocking i/o
pt->abort_errno = EINTR;
__sig_cancel(pt); // we can wake it up immediately by canceling it
return 0;
}

View file

@ -26,7 +26,7 @@ int __sig_check(void);
int __sig_kill(struct PosixThread *, int, int);
int __sig_mask(int, const sigset_t *, sigset_t *);
int __sig_raise(int, int);
void __sig_cancel(struct PosixThread *, unsigned);
void __sig_cancel(struct PosixThread *);
void __sig_generate(int, int);
void __sig_init(void);

View file

@ -507,9 +507,6 @@ int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact) {
once = true;
}
}
if (IsWindows() && !rc && sig == SIGWINCH) {
_init_sigwinch(); // lazy b/c sigwinch is otherwise ignored
}
}
STRACE("sigaction(%G, %s, [%s]) → %d% m", sig, DescribeSigaction(0, act),
DescribeSigaction(rc, oldact), rc);

View file

@ -1,90 +0,0 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2021 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/atomic.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/cosmo.h"
#include "libc/nt/console.h"
#include "libc/nt/enum/processcreationflags.h"
#include "libc/nt/struct/consolescreenbufferinfoex.h"
#include "libc/nt/synchronization.h"
#include "libc/nt/thread.h"
#include "libc/nt/thunk/msabi.h"
#include "libc/runtime/runtime.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/sig.h"
#include "libc/thread/tls.h"
#include "libc/thread/tls2.internal.h"
#ifdef __x86_64__
intptr_t __sigwinch_thread;
static unsigned __sigwinch_size;
static atomic_uint __sigwinch_once;
static struct CosmoTib __sigwinch_tls;
static textwindows unsigned __get_console_size(void) {
unsigned res = -1u;
__fds_lock();
for (int fd = 1; fd < 10; ++fd) {
intptr_t hConsoleOutput;
if (g_fds.p[fd].kind == kFdConsole) {
hConsoleOutput = g_fds.p[fd].extra;
} else {
hConsoleOutput = g_fds.p[fd].handle;
}
struct NtConsoleScreenBufferInfoEx sr = {.cbSize = sizeof(sr)};
if (GetConsoleScreenBufferInfoEx(hConsoleOutput, &sr)) {
unsigned short yn = sr.srWindow.Bottom - sr.srWindow.Top + 1;
unsigned short xn = sr.srWindow.Right - sr.srWindow.Left + 1;
res = (unsigned)yn << 16 | xn;
break;
}
}
__fds_unlock();
return res;
}
static textwindows dontinstrument uint32_t __sigwinch_worker(void *arg) {
__bootstrap_tls(&__sigwinch_tls, __builtin_frame_address(0));
for (;;) {
unsigned old = __sigwinch_size;
unsigned neu = __get_console_size();
if (neu != old) {
__sigwinch_size = neu;
__sig_generate(SIGWINCH, SI_KERNEL);
}
SleepEx(25, false);
}
return 0;
}
static textwindows void __sigwinch_init(void) {
__enable_threads();
__sigwinch_size = __get_console_size();
__sigwinch_thread = CreateThread(0, 65536, __sigwinch_worker, 0,
kNtStackSizeParamIsAReservation, 0);
}
textwindows void _init_sigwinch(void) {
cosmo_once(&__sigwinch_once, __sigwinch_init);
}
#endif /* __x86_64__ */

View file

@ -24,18 +24,22 @@ COSMOPOLITAN_C_START_
#define kFdTtyMunging 4 /* enable input / output remappings */
#define kFdTtyNoCr2Nl 8 /* don't map \r → \n (a.k.a !ICRNL) */
#define kFdTtyNoIsigs 16
#define kFdTtyNoBlock 32
#define kFdTtyXtMouse 64
struct Fd {
char kind;
bool eoftty;
bool dontclose;
char buflen;
char buf[4];
unsigned char buflen;
unsigned flags;
unsigned mode;
int64_t handle;
int64_t extra;
int64_t pointer;
pthread_mutex_t lock;
unsigned char mousebuttons;
char buf[32];
};
struct StdinRelay {
@ -55,11 +59,6 @@ struct Fds {
struct StdinRelay stdin;
};
void WinMainStdin(void);
int64_t __resolve_stdin_handle(int64_t);
int __munge_terminal_input(char *, uint32_t *);
void __echo_terminal_input(struct Fd *, char *, size_t);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_FD_INTERNAL_H_ */

View file

@ -64,9 +64,6 @@ const char *DescribeSigaction(char[256], int, const struct sigaction *);
#define DescribeSigaction(rc, sa) DescribeSigaction(alloca(256), rc, sa)
void _init_onntconsoleevent(void);
void _init_sigwinch(void);
extern intptr_t __sigwinch_thread;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -55,13 +55,14 @@ textwindows int tcgetattr_nt(int fd, struct termios *tio) {
bzero(tio, sizeof(*tio));
tio->c_cc[VMIN] = 1;
tio->c_cc[VMIN] = !(__ttymagic & kFdTtyNoBlock);
tio->c_cc[VEOF] = __veof;
tio->c_cc[VTIME] = __vtime;
tio->c_cc[VINTR] = __vintr;
tio->c_cc[VQUIT] = __vquit;
tio->c_cc[VERASE] = CTRL('?');
tio->c_cc[VWERASE] = CTRL('W');
tio->c_cc[VKILL] = CTRL('U');
tio->c_cc[VEOF] = CTRL('D');
tio->c_cc[VMIN] = CTRL('A');
tio->c_cc[VSTART] = _POSIX_VDISABLE;
tio->c_cc[VSTOP] = _POSIX_VDISABLE;

View file

@ -45,130 +45,6 @@
#ifdef __x86_64__
textwindows int __munge_terminal_input(char *p, uint32_t *n) {
size_t i, j;
if (!(__ttymagic & kFdTtyNoCr2Nl)) {
for (i = 0; i < *n; ++i) {
if (p[i] == '\r') {
p[i] = '\n';
}
}
}
bool delivered = false;
bool got_vintr = false;
bool got_vquit = false;
for (j = i = 0; i < *n; ++i) {
/*
It's not possible to type Ctrl+Space (aka ^@) into the CMD.EXE
console. Ctrl+Z is also problematic, since the Windows Console
processes that keystroke into an EOF event, even when we don't
enable input processing. These control codes are important for
Emacs users. One solution is to install the "Windows Terminal"
application from Microsoft Store, type Ctrl+Shift+, to get its
settings.json file opened, and add this code to its "actions":
"actions": [
{
"command": {
"action": "sendInput",
"input": ""
},
"keys": "ctrl+space"
},
{
"command": {
"action": "sendInput",
"input": ""
},
"keys": "ctrl+z"
},
{
"command": {
"action": "sendInput",
"input": "\u001f"
},
"keys": "ctrl+-"
}
],
Its not possible to configure Windows Terminal to output our
control codes. The workaround is to encode control sequences
using the "Control Pictures" UNICODE plane, which we'll then
translate back from UTF-8 glyphs, into actual control codes.
Another option Windows users can consider, particularly when
using CMD.EXE is installing Microsoft PowerTools whech makes
it possible to remap keys then configure ncurses to use them
https://github.com/microsoft/terminal/issues/2865
*/
int c;
if (i + 3 <= *n && // control pictures ascii nul ␀
(p[i + 0] & 255) == 0xe2 && // becomes ^@ a.k.a. ctrl-space
(p[i + 1] & 255) == 0x90 && // map the entire unicode plane
((p[i + 2] & 255) >= 0x80 && //
(p[i + 2] & 255) <= 0x9F)) {
c = (p[i + 2] & 255) - 0x80;
i += 2;
} else {
c = p[i] & 255;
}
if (!(__ttymagic & kFdTtyNoIsigs) && //
__vintr != _POSIX_VDISABLE && //
c == __vintr) {
got_vintr = true;
} else if (!(__ttymagic & kFdTtyNoIsigs) && //
__vquit != _POSIX_VDISABLE && //
c == __vquit) {
got_vquit = true;
} else {
p[j++] = c;
}
}
if (got_vintr) {
__sig_raise(SIGINT, SI_KERNEL);
delivered |= true;
}
if (got_vquit) {
__sig_raise(SIGQUIT, SI_KERNEL);
delivered |= true;
}
if (*n && !j) {
if (delivered) {
return DO_EINTR;
} else {
return DO_RESTART;
}
}
*n = j;
return DO_NOTHING;
}
// Manual CMD.EXE echoing for when !ICANON && ECHO is the case.
textwindows void __echo_terminal_input(struct Fd *fd, char *p, size_t n) {
int64_t hOutput;
if (fd->kind == kFdConsole) {
hOutput = fd->extra;
} else {
hOutput = g_fds.p[1].handle;
}
if (__ttymagic & kFdTtyEchoRaw) {
WriteFile(hOutput, p, n, 0, 0);
} else {
size_t i;
for (i = 0; i < n; ++i) {
if (isascii(p[i]) && iscntrl(p[i]) && p[i] != '\n' && p[i] != '\t') {
char ctl[2];
ctl[0] = '^';
ctl[1] = p[i] ^ 0100;
WriteFile(hOutput, ctl, 2, 0, 0);
} else {
WriteFile(hOutput, p + i, 1, 0, 0);
}
}
}
}
textwindows int tcsetattr_nt(int fd, int opt, const struct termios *tio) {
bool32 ok;
int64_t hInput, hOutput;
@ -198,18 +74,15 @@ textwindows int tcsetattr_nt(int fd, int opt, const struct termios *tio) {
inmode |= kNtEnableWindowInput;
__ttymagic = 0;
if (tio->c_lflag & ICANON) {
inmode |= kNtEnableLineInput | kNtEnableProcessedInput;
inmode |=
kNtEnableLineInput | kNtEnableProcessedInput | kNtEnableQuickEditMode;
} else {
inmode &= ~kNtEnableQuickEditMode;
__ttymagic |= kFdTtyMunging;
if (tio->c_cc[VMIN] != 1) {
STRACE("tcsetattr c_cc[VMIN] must be 1 on Windows");
return einval();
}
if (IsAtLeastWindows10()) {
// - keys like f1, up, etc. get turned into \e ansi codes
// - totally destroys default console gui (e.g. up arrow)
inmode |= kNtEnableVirtualTerminalInput;
if (!tio->c_cc[VMIN]) {
__ttymagic |= kFdTtyNoBlock;
}
__vtime = tio->c_cc[VTIME];
}
if (!(tio->c_iflag & ICRNL)) {
__ttymagic |= kFdTtyNoCr2Nl;
@ -233,6 +106,7 @@ textwindows int tcsetattr_nt(int fd, int opt, const struct termios *tio) {
if (!(tio->c_lflag & ISIG)) {
__ttymagic |= kFdTtyNoIsigs;
}
__veof = tio->c_cc[VEOF];
__vintr = tio->c_cc[VINTR];
__vquit = tio->c_cc[VQUIT];
if ((tio->c_lflag & ISIG) && //
@ -261,11 +135,4 @@ textwindows int tcsetattr_nt(int fd, int opt, const struct termios *tio) {
return 0;
}
__attribute__((__constructor__)) static void tcsetattr_nt_init(void) {
if (!getenv("TERM")) {
setenv("TERM", "xterm-256color", true);
errno = 0; // ignore malloc not linked
}
}
#endif /* __x86_64__ */

View file

@ -1,94 +0,0 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2023 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/internal.h"
#include "libc/dce.h"
#include "libc/nt/console.h"
#include "libc/nt/createfile.h"
#include "libc/nt/enum/accessmask.h"
#include "libc/nt/enum/creationdisposition.h"
#include "libc/nt/enum/fileflagandattributes.h"
#include "libc/nt/enum/processcreationflags.h"
#include "libc/nt/ipc.h"
#include "libc/nt/runtime.h"
#include "libc/nt/synchronization.h"
#include "libc/nt/thread.h"
#include "libc/thread/tls.h"
static textwindows dontinstrument wontreturn void WinStdinThread(void) {
char buf[4];
struct CosmoTib tls;
uint32_t i, got, wrote;
__bootstrap_tls(&tls, __builtin_frame_address(0));
for (;;) {
WaitForSingleObject(g_fds.stdin.inisem, -1u);
if (!ReadFile(g_fds.stdin.handle, buf, sizeof(buf), &got, 0)) goto Finish;
if (!got) goto Finish;
for (i = 0; i < got; i += wrote) {
if (!WriteFile(g_fds.stdin.writer, buf + i, got - i, &wrote, 0)) {
goto Finish;
}
}
}
Finish:
CloseHandle(g_fds.stdin.writer);
ExitThread(0);
}
textwindows static char16_t *FixCpy(char16_t p[17], uint64_t x, uint8_t k) {
while (k > 0) *p++ = "0123456789abcdef"[(x >> (k -= 4)) & 15];
*p = '\0';
return p;
}
textwindows static char16_t *CreateStdinPipeName(char16_t *a, int64_t h) {
char16_t *p = a;
const char *q = "\\\\?\\pipe\\cosmo\\stdin\\";
while (*q) *p++ = *q++;
p = FixCpy(p, h, 64);
*p = 0;
return a;
}
textwindows void WinMainStdin(void) {
uint32_t conmode;
char16_t pipename[64];
int64_t hStdin, hWriter, hReader, hThread, hSemaphore;
if (!SupportsWindows()) return;
hStdin = GetStdHandle(kNtStdInputHandle);
if (!GetConsoleMode(hStdin, &conmode)) return;
CreateStdinPipeName(pipename, hStdin);
hWriter = CreateNamedPipe(
pipename, kNtPipeAccessOutbound | kNtFileFlagOverlapped,
kNtPipeTypeMessage | kNtPipeReadmodeMessage | kNtPipeNowait,
kNtPipeUnlimitedInstances, 4096, 4096, 0, 0);
if (hWriter == kNtInvalidHandleValue) return;
hReader = CreateFile(pipename, kNtGenericRead, 0, 0, kNtOpenExisting,
kNtFileFlagOverlapped, 0);
if (hReader == kNtInvalidHandleValue) return;
hSemaphore = CreateSemaphore(0, 0, 1, 0);
if (!hSemaphore) return;
hThread = CreateThread(0, 65536, (void *)WinStdinThread, 0,
kNtStackSizeParamIsAReservation, 0);
if (!hThread) return;
g_fds.stdin.handle = hStdin;
g_fds.stdin.thread = hThread;
g_fds.stdin.reader = hReader;
g_fds.stdin.writer = hWriter;
g_fds.stdin.inisem = hSemaphore;
}

View file

@ -23,9 +23,12 @@
#include "libc/calls/struct/iovec.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/errno.h"
#include "libc/intrin/nomultics.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/macros.internal.h"
#include "libc/nt/console.h"
#include "libc/nt/enum/consolemodeflags.h"
#include "libc/nt/enum/filetype.h"
#include "libc/nt/enum/wait.h"
#include "libc/nt/errors.h"
@ -46,6 +49,13 @@
__msabi extern typeof(CloseHandle) *const __imp_CloseHandle;
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 ssize_t sys_write_nt_impl(int fd, void *data, size_t size,
ssize_t offset) {
@ -81,6 +91,76 @@ static textwindows ssize_t sys_write_nt_impl(int fd, void *data, size_t size,
offset = f->pointer;
}
// To use the tty mouse events feature:
// - write(1, "\e[?1000;1002;1015;1006h") to enable
// - write(1, "\e[?1000;1002;1015;1006l") to disable
// See o//examples/ttyinfo.com and o//tool/viz/life.com
uint32_t cm;
if (!seekable && (f->kind == kFdConsole || GetConsoleMode(handle, &cm))) {
int64_t hin;
if (f->kind == kFdConsole) {
hin = f->handle;
} else {
hin = GetStdHandle(kNtStdInputHandle);
}
if (GetConsoleMode(hin, &cm)) {
int t = 0;
unsigned x;
bool m = false;
char *p = data;
uint32_t cm2 = cm;
for (int i = 0; i < size; ++i) {
switch (t) {
case 0:
if (p[i] == 033) {
t = 1;
}
break;
case 1:
if (p[i] == '[') {
t = 2;
} else {
t = 0;
}
break;
case 2:
if (p[i] == '?') {
t = 3;
x = 0;
} else {
t = 0;
}
break;
case 3:
if ('0' <= p[i] && p[i] <= '9') {
x *= 10;
x += p[i] - '0';
} else if (p[i] == ';') {
m |= IsMouseModeCommand(x);
x = 0;
} else {
m |= IsMouseModeCommand(x);
if (p[i] == 'h') {
__ttymagic |= kFdTtyXtMouse;
cm2 |= kNtEnableMouseInput;
cm2 &= kNtEnableQuickEditMode; // precludes mouse events
} else if (p[i] == 'l') {
__ttymagic &= ~kFdTtyXtMouse;
cm2 |= kNtEnableQuickEditMode; // disables mouse too
}
t = 0;
}
break;
default:
__builtin_unreachable();
}
}
if (cm2 != cm) {
SetConsoleMode(hin, cm2);
}
}
}
struct NtOverlapped overlap = {.hEvent = CreateEvent(0, 0, 0, 0),
.Pointer = offset};
ok = WriteFile(handle, data, size, 0, &overlap);
@ -143,7 +223,6 @@ static textwindows ssize_t sys_write_nt_impl(int fd, void *data, size_t size,
_weaken(__sig_raise)(SIGPIPE, SI_KERNEL);
return epipe();
} else {
STRACE("broken pipe");
TerminateThisProcess(SIGPIPE);
}
case kNtErrorAccessDenied: // write doesn't return EACCESS

View file

@ -18,6 +18,7 @@
*/
#include "libc/calls/struct/termios.h"
#include "libc/calls/struct/termios.internal.h"
#include "libc/calls/ttydefaults.h"
#include "libc/dce.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/describeflags.internal.h"
@ -119,8 +120,15 @@ const char *(DescribeTermios)(char buf[N], ssize_t rc,
{IEXTEN, "IEXTEN"}, //
{EXTPROC, "EXTPROC"}, //
};
append(", .c_lflag=%s",
DescribeFlags(b128, 128, kLocal, ARRAYLEN(kLocal), "", tio->c_lflag));
append(", "
".c_lflag=%s, "
".c_cc[VMIN]=%d, "
".c_cc[VTIME]=%d, "
".c_cc[VINTR]=CTRL(%#c), "
".c_cc[VQUIT]=CTRL(%#c)",
DescribeFlags(b128, 128, kLocal, ARRAYLEN(kLocal), "", tio->c_lflag),
tio->c_cc[VMIN], tio->c_cc[VTIME], CTRL(tio->c_cc[VINTR]),
CTRL(tio->c_cc[VQUIT]));
append("}");

View file

@ -119,6 +119,7 @@ textstartup void __init_fds(int argc, char **argv, char **envp) {
}
fds->p[1].flags = O_WRONLY | O_APPEND;
fds->p[2].flags = O_WRONLY | O_APPEND;
__veof = CTRL('D');
__vintr = CTRL('C');
__vquit = CTRL('\\');
}

View file

@ -18,15 +18,10 @@
*/
#include "libc/intrin/nomultics.internal.h"
/**
* Controls ANSI prefix for log emissions.
*
* This should be true in raw tty mode repls.
*
* @see kprintf(), vflogf(), linenoise()
*/
char __replmode;
char __replstderr;
char __ttymagic;
char __vintr;
char __vquit;
unsigned char __replmode;
unsigned char __replstderr;
unsigned char __ttymagic;
unsigned char __veof;
unsigned char __vintr;
unsigned char __vquit;
unsigned char __vtime;

View file

@ -1,13 +1,16 @@
#ifndef COSMOPOLITAN_LIBC_INTRIN_NOMULTICS_INTERNAL_H_
#define COSMOPOLITAN_LIBC_INTRIN_NOMULTICS_INTERNAL_H_
#include "libc/calls/struct/timespec.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
extern char __replmode;
extern char __replstderr;
extern char __ttymagic;
extern char __vintr;
extern char __vquit;
extern unsigned char __replmode;
extern unsigned char __replstderr;
extern unsigned char __ttymagic;
extern unsigned char __veof;
extern unsigned char __vintr;
extern unsigned char __vquit;
extern unsigned char __vtime;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -49,8 +49,8 @@ bool32 ReadConsoleInput(int64_t hConsoleInput, struct NtInputRecord *lpBuffer,
uint32_t nLength, uint32_t *lpNumberOfEventsRead);
bool32 PeekConsoleInput(int64_t hConsoleInput, struct NtInputRecord *lpBuffer,
uint32_t nLength, uint32_t *lpNumberOfEventsRead);
bool32 GetNumberOfConsoleInputEvent(int64_t hConsoleInput,
uint32_t *lpNumberOfEvents);
bool32 GetNumberOfConsoleInputEvents(int64_t hConsoleInput,
uint32_t *lpNumberOfEvents);
bool32 ReadConsoleOutput(int64_t hConsoleOutput, struct NtCharInfo *lpBuffer,
struct NtCoord dwBufferSize,
struct NtCoord dwBufferCoord,

View file

@ -56,7 +56,8 @@ int64_t OpenProcess(uint32_t dwDesiredAccess, bool32 bInheritHandle,
uint32_t GetCurrentProcessId(void); /* %gs:0x40 */
uint32_t GetEnvironmentVariable(const char16_t *lpName, char16_t *lpBuffer,
uint32_t nSize);
uint32_t SetEnvironmentVariable(const char16_t *lpName, char16_t *lpValue);
uint32_t SetEnvironmentVariable(const char16_t *lpName,
const char16_t *lpValue);
int32_t SetEnvironmentStrings(char16_t *NewEnvironment);
bool32 GetProcessAffinityMask(int64_t hProcess, uint64_t *lpProcessAffinityMask,
uint64_t *lpSystemAffinityMask);

View file

@ -9,17 +9,35 @@ struct NtKeyEventRecord {
uint16_t wVirtualKeyCode;
uint16_t wVirtualScanCode;
union {
int16_t UnicodeChar;
uint16_t UnicodeChar;
char AsciiChar;
} uChar;
unsigned int dwControlKeyState;
#define kNtRightAltPressed 0x0001
#define kNtLeftAltPressed 0x0002
#define kNtRightCtrlPressed 0x0004
#define kNtLeftCtrlPressed 0x0008
#define kNtShiftPressed 0x0010
#define kNtNumlockOn 0x0020
#define kNtScrolllockOn 0x0040
#define kNtCapslockOn 0x0080
#define kNtEnhancedKey 0x0100
};
struct NtMouseEventRecord {
struct NtCoord dwMousePosition;
uint32_t dwButtonState;
#define kNtFromLeft1stButtonPressed 0x0001
#define kNtRightmostButtonPressed 0x0002
#define kNtFromLeft2ndButtonPressed 0x0004
#define kNtFromLeft3rdButtonPressed 0x0008
#define kNtFromLeft4thButtonPressed 0x0010
uint32_t dwControlKeyState;
uint32_t dwEventFlags;
#define kNtMouseMoved 0x0001
#define kNtDoubleClick 0x0002
#define kNtMouseWheeled 0x0004
#define kNtMouseHwheeled 0x0008
};
struct NtWindowBufferSizeRecord {
@ -36,6 +54,11 @@ struct NtFocusEventRecord {
struct NtInputRecord {
uint16_t EventType;
#define kNtKeyEvent 0x0001
#define kNtMouseEvent 0x0002
#define kNtWindowBufferSizeEvent 0x0004
#define kNtMenuEvent 0x0008
#define kNtFocusEvent 0x0010
union {
struct NtKeyEventRecord KeyEvent;
struct NtMouseEventRecord MouseEvent;

View file

@ -154,9 +154,6 @@ keywords int sys_execve_nt(const char *program, char *const argv[],
PurgeThread(pt->tib->tib_syshand);
PurgeHandle(pt->semaphore);
}
if (_weaken(__sigwinch_thread)) {
PurgeThread(*_weaken(__sigwinch_thread));
}
if (_weaken(__itimer)) {
PurgeThread(_weaken(__itimer)->thread);
}

View file

@ -55,12 +55,14 @@ __msabi extern typeof(FreeEnvironmentStrings) *const __imp_FreeEnvironmentString
__msabi extern typeof(GetConsoleMode) *const __imp_GetConsoleMode;
__msabi extern typeof(GetCurrentProcessId) *const __imp_GetCurrentProcessId;
__msabi extern typeof(GetEnvironmentStrings) *const __imp_GetEnvironmentStringsW;
__msabi extern typeof(GetEnvironmentVariable) *const __imp_GetEnvironmentVariableW;
__msabi extern typeof(GetFileAttributes) *const __imp_GetFileAttributesW;
__msabi extern typeof(GetStdHandle) *const __imp_GetStdHandle;
__msabi extern typeof(MapViewOfFileEx) *const __imp_MapViewOfFileEx;
__msabi extern typeof(SetConsoleCP) *const __imp_SetConsoleCP;
__msabi extern typeof(SetConsoleMode) *const __imp_SetConsoleMode;
__msabi extern typeof(SetConsoleOutputCP) *const __imp_SetConsoleOutputCP;
__msabi extern typeof(SetEnvironmentVariable) *const __imp_SetEnvironmentVariableW;
__msabi extern typeof(SetStdHandle) *const __imp_SetStdHandle;
__msabi extern typeof(VirtualProtect) *const __imp_VirtualProtect;
// clang-format on
@ -92,8 +94,7 @@ __funline char16_t *MyCommandLine(void) {
// implements all win32 apis on non-windows hosts
static abi long __oops_win32(void) {
assert(!"win32 api called on non-windows host");
return 0;
notpossible;
}
// returns true if utf-8 path is a win32-style path that exists
@ -131,11 +132,16 @@ static abi wontreturn void WinInit(const char16_t *cmdline) {
(intptr_t)v_ntsubsystem == kNtImageSubsystemWindowsCui) {
__imp_SetConsoleCP(kNtCpUtf8);
__imp_SetConsoleOutputCP(kNtCpUtf8);
for (int i = 1; i <= 2; ++i) {
for (int i = 0; i <= 2; ++i) {
uint32_t m;
intptr_t h = __imp_GetStdHandle(kNtStdio[i]);
__imp_GetConsoleMode(h, &m);
__imp_SetConsoleMode(h, m | kNtEnableVirtualTerminalProcessing);
if (!i) {
m |= kNtEnableMouseInput | kNtEnableWindowInput;
} else {
m |= kNtEnableVirtualTerminalProcessing;
}
__imp_SetConsoleMode(h, m);
}
}
@ -222,9 +228,6 @@ abi int64_t WinMain(int64_t hInstance, int64_t hPrevInstance,
_weaken(WinSockInit)();
}
DeduplicateStdioHandles();
if (_weaken(WinMainStdin)) {
_weaken(WinMainStdin)();
}
if (_weaken(WinMainForked)) {
_weaken(WinMainForked)();
}

View file

@ -111,23 +111,27 @@ static void _pthread_cancel_listen(void) {
static void pthread_cancel_nt(struct PosixThread *pt, intptr_t hThread) {
uint32_t old_suspend_count;
if ((pt->pt_flags & PT_ASYNC) && !(pt->pt_flags & PT_NOCANCEL)) {
if ((old_suspend_count = SuspendThread(hThread)) != -1u) {
if (!(pt->pt_flags & PT_NOCANCEL) &&
(pt->pt_flags & (PT_ASYNC | PT_MASKED))) {
pt->pt_flags |= PT_NOCANCEL;
pt->abort_errno = ECANCELED;
if ((pt->pt_flags & PT_ASYNC) &&
(old_suspend_count = SuspendThread(hThread)) != -1u) {
if (!old_suspend_count) {
struct NtContext cpu;
cpu.ContextFlags = kNtContextControl | kNtContextInteger;
if (GetThreadContext(hThread, &cpu)) {
pt->pt_flags |= PT_NOCANCEL;
cpu.Rip = (uintptr_t)pthread_exit;
cpu.Rdi = (uintptr_t)PTHREAD_CANCELED;
cpu.Rsp &= -16;
*(uintptr_t *)(cpu.Rsp -= sizeof(uintptr_t)) = cpu.Rip;
pt->abort_errno = ECANCELED;
unassert(SetThreadContext(hThread, &cpu));
__sig_cancel(pt, 0);
}
}
ResumeThread(hThread);
}
__sig_cancel(pt);
}
}

View file

@ -95,3 +95,15 @@ TEST(fionread, pipe) {
ASSERT_SYS(0, 0, close(pfds[1]));
ASSERT_SYS(0, 0, close(pfds[0]));
}
TEST(fionread, eof_returnsZeroWithoutError) {
char buf[8];
int pfds[2];
int pending;
ASSERT_SYS(0, 0, pipe(pfds));
ASSERT_SYS(0, 0, close(pfds[1]));
ASSERT_SYS(0, 0, ioctl(pfds[0], FIONREAD, &pending));
ASSERT_EQ(0, pending);
ASSERT_SYS(0, 0, read(pfds[0], buf, 8));
ASSERT_SYS(0, 0, close(pfds[0]));
}