review BLOCKER: PTY input forwarding + raw mode toggle
Phase 1 review caught a structural gap: executor.exec only drained the
PTY master fd, never forwarded user keystrokes — vim/less/htop/nano
would render and hang on input. PHASE1.md §5 specified bidirectional
multiplex but only the read leg landed. tcgetattr/tcsetattr were also
missing, so even with input forwarding the parent's line discipline
would buffer until newline (breaking single-key UIs).
ffi/libc:
- struct termios opaque buffer + tcgetattr/tcsetattr + cfmakeraw
- M.set_raw(fd) saves termios + applies cfmakeraw; returns saved or
(nil, err) when fd isn't a tty (scripted / piped-stdin runs)
- M.restore_termios(fd, saved)
- struct pollfd + M.poll (POLLIN constant)
executor:
- multiplex(sess): poll(stdin, master); reads master on any revents
(POLLHUP fires when child closes its slave end, not POLLIN — the
revents != 0 check catches both); forwards stdin keystrokes to
master; loop exits when master read returns 0 (EOF / child gone)
- stdin polling is only enabled when stdin_is_tty (set_raw succeeded);
piped-stdin runs (tests / scripted) would otherwise drain queued
aish commands into the child of the *current* cmd, swallowing them
- raw mode is restored before returning so the user lands back at the
aish prompt in canonical mode
renderer + repl:
- exec_output(out, code) split into exec_begin() (top rule, before
spawn) + exec_end(code) (closing rule with exit, after wait). PTY
multiplex streams the body live to stdout in between; the renderer
never re-prints the body.
PHASE1.md §3:
- tcgetattr/tcsetattr changed from "optional" to "required for
single-key UIs to work — done-criteria #2"; poll added to the libc
row description.
Verified:
- non-interactive smoke (echo / false / exit 7 / ls /nonexistent /
printf multi-line) — all exit codes correct, output streamed live,
a\nb\nc\n preserved byte-for-byte
- scripted-stdin run reaches all expected lines (no stdin draining
into a non-interactive child)
- aish prompt + framed exec block + exit-code line all render in
correct order
Live interactive verification (vim / less / htop in a real terminal)
still needs a user-test pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,18 @@ ssize_t read (int fd, void *buf, size_t count);
|
||||
ssize_t write (int fd, const void *buf, size_t count);
|
||||
int close (int fd);
|
||||
int kill (pid_t pid, int sig);
|
||||
|
||||
/* termios for raw-mode toggle around interactive PTY children. The struct
|
||||
is treated as opaque — cfmakeraw fills it; size 64 is comfortably larger
|
||||
than glibc's struct termios (60 bytes) on aarch64/x86_64 Linux. */
|
||||
struct termios { char _opaque[64]; };
|
||||
int tcgetattr(int fd, struct termios *tio);
|
||||
int tcsetattr(int fd, int actions, const struct termios *tio);
|
||||
void cfmakeraw(struct termios *tio);
|
||||
|
||||
/* poll for stdin↔master multiplex in executor. */
|
||||
struct pollfd { int fd; short events; short revents; };
|
||||
int poll(struct pollfd *fds, unsigned long nfds, int timeout);
|
||||
]]
|
||||
|
||||
local C = ffi.C
|
||||
@@ -106,4 +118,37 @@ function M.kill(pid, sig)
|
||||
return false, ffi.string(C.strerror(C.__errno_location()[0]))
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------- termios
|
||||
-- Save current tty mode and switch to raw via cfmakeraw. Returns the saved
|
||||
-- termios pointer (to be passed back to M.restore_termios) or (nil, err) if
|
||||
-- fd isn't a tty (e.g. stdin redirected from a file in CI / scripted runs).
|
||||
local TCSANOW = 0
|
||||
|
||||
function M.set_raw(fd)
|
||||
local saved = ffi.new("struct termios")
|
||||
if C.tcgetattr(fd, saved) < 0 then
|
||||
return nil, M.strerror(M.errno())
|
||||
end
|
||||
local raw = ffi.new("struct termios")
|
||||
ffi.copy(raw, saved, ffi.sizeof("struct termios"))
|
||||
C.cfmakeraw(raw)
|
||||
if C.tcsetattr(fd, TCSANOW, raw) < 0 then
|
||||
return nil, M.strerror(M.errno())
|
||||
end
|
||||
return saved
|
||||
end
|
||||
|
||||
function M.restore_termios(fd, saved)
|
||||
return C.tcsetattr(fd, TCSANOW, saved) == 0
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------- poll
|
||||
M.POLLIN = 0x0001
|
||||
M.EINTR = 4
|
||||
|
||||
-- Returns: rc (>= 0 fds ready, 0 timeout, -1 error)
|
||||
function M.poll(fds_arr, nfds, timeout_ms)
|
||||
return C.poll(fds_arr, nfds, timeout_ms or -1)
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
Reference in New Issue
Block a user