Goal: Create an LD_PRELOAD where the following test Python code does no syscalls, and still "works". >>> from os import * >>> from pty import * >>> ptmx, pts = openpty() >>> write(ptmx, 'lol\x7fcal\n') 8 >>> read(ptmx, 1024) 'lol\x08 \x08cal\r\n' >>> read(pts, 1024) 'local\n' Improve it so that if the file descriptor is passed to a child process, the input canonicalization still happens. (FIXME: Create test program?) Improve it so that window size can be sent between processes, too. (I know bash uses this, so we're going to need it anyway.) Improve it so that we can run an ajax terminal app and get bash to launch and have bash perceive input canonicalized in the right way. Protocol: The general protocol idea is to stay as close to real pty semantics as possible, so we have to intercept as few functions as possible. There's a trusted mediator process, ptyd, that handles the actual termios implementation, plus an LD_PRELOAD, libptyd.so, to turn termios calls into RPCs to ptyd. ptyd uses sockets in a well-known directory $PTYD_DIR. As with kernel termios, a pty master or a pty terminal is a file descriptor you can read or write text from. In our implementation, it's a UNIX socket. Metadata is sent via a capnp-rpc channel over a UNIX socket. To get a metadata channel, send a message over the socket (it can be empty) with SCM_RIGHTS passing one end of a socketpair(). To close it, simply close that fd. You can have multiple metadata channels for the same pty fd; they're equivalent. To get a pty master, connect to the UNIX socket $PTYD_DIR/ptmx. (You can't directly open() a UNIX socket, but if you could, $PTYD_DIR closely resembles /dev/pts.) To get the name of the associated terminal, invoke the ptsname RPC. This will return a string of the form $PTYD_DIR/n, where n is an integer. This is the filename of a new UNIX socket corresponding to the other end of the terminal; the client application (shell, etc.) can connect to that. Since we're not the kernel, we don't get the benefit of the open(2) behavior where opening a terminal without O_NOCTTY makes it your controlling terminal. So you need to explicitly invoke the ioc_sctty RPC so we know where to send signals to. Since we're not the kernel, we can't implement job control properly. So libptyd's tcsetpgrp() just returns an error. RPCs permissible on the master: ptsname() -> string RPCs permissible on the terminal: tioc_sti(char) tioc_sctty() tioc_notty() RPCs permissible on both: tcgetattr() -> struct termios tcsetattr(int actions, struct termios termios) tioc_gwinsz() -> (uint, uint) tioc_swinsz(uint, uint) Unimplemented functions: tcgetpgrp / tcsetpgrp (we can't do job control) grantpt / unlockpt (no-ops) relevant system or libc calls: * open("/dev/ptmx") * posix_openpt * open("/dev/pts/...") * ptsname * openpty