Path: blob/main/core/kernel/src/wasm/posix/termios.ts
1068 views
/*12RANDOM NOTES34MAJOR TODO: For xterm.js entirely in browser (and MS Windows), we may still5have to implement this stuff. Hopefully this will be much easier, since we6implemented everything via our posix-node and can observe what's expected7by programs.89Also, for example, one of the flags is1011"ISIG When any of the characters INTR, QUIT, SUSP, or DSUSP are received, generate the corresponding signal."1213and since we are implementing signals and watching characters, of course this is some14logic that we would do.1516On a POSIX server, a complete and easy option is to directly call the c library via17an extension module, translating flags back and forth between native and wasi,18and we do exactly that here.1920Right now on non-POSIX, the following are partially stub functions, but not minimal.2122IMPORTANT! We can't do NOTHING! For example, libedit will23randomly not work if we do nothing (which drove me crazy for days)!24This is because of the line2526if (tcgetattr(fileno(rl_instream), &t) != -1 && (t.c_lflag & ECHO) == 0)2728in packages/libedit/build/wasm/src/readline.c. If t.c_lflag doesn't have the ECHO29flag, then libedit will be totally broken for interactive use.30We set at least that below for fd=0 and intend to do more (TODO!).3132tcflag_t c_iflag; // input modes33tcflag_t c_oflag; // output modes34tcflag_t c_cflag; // control modes35tcflag_t c_lflag; // local modes36cc_t c_cc[NCCS]; /; special characters3738I think for us c_lflag is mostly what matters.3940Another key point that is subtle, is we can't just worry about a subset of "official41posix flags" and forget about the rest. We have to see what was changed at the42wasi level, then modify exactly what is true natively to match that. This makes43the code below a bit odd.44*/4546import debug from "debug";47import constants from "./constants";4849const log = debug("posix:termios");5051const FLAGS = {52c_iflag: [53"IGNBRK",54"BRKINT",55"IGNPAR",56"PARMRK",57"INPCK",58"ISTRIP",59"INLCR",60"IGNCR",61"ICRNL",62"IXON",63"IXANY",64"IXOFF",65"IMAXBEL",66"IUTF8",67],68c_oflag: ["OPOST", "ONLCR", "OCRNL", "ONOCR", "ONLRET", "OFILL", "OFDEL"],69c_cflag: [70"CSIZE",71"CS5",72"CS6",73"CS7",74"CS8",75"CSTOPB",76"CREAD",77"PARENB",78"PARODD",79"HUPCL",80"CLOCAL",81],82c_lflag: [83"ISIG",84"ICANON",85"ECHO",86"ECHOE",87"ECHOK",88"ECHONL",89"NOFLSH",90"TOSTOP",91"IEXTEN",92],93} as const;9495interface Termios {96c_iflag: number;97c_oflag: number;98c_cflag: number;99c_lflag: number;100}101102export default function termios({ posix, recv, send, wasi, noStdio }) {103function termios_set(tioPtr: number, { c_iflag, c_oflag, c_cflag, c_lflag }) {104const size = 4;105send.u32(tioPtr, c_iflag ?? 0);106send.u32(tioPtr + size, c_oflag ?? 0);107send.u32(tioPtr + 2 * size, c_cflag ?? 0);108send.u32(tioPtr + 3 * size, c_lflag ?? 0);109}110111function termios_get(tioPtr: number): {112c_iflag: number;113c_oflag: number;114c_cflag: number;115c_lflag: number;116} {117const size = 4;118return {119c_iflag: recv.u32(tioPtr),120c_oflag: recv.u32(tioPtr + size),121c_cflag: recv.u32(tioPtr + 2 * size),122c_lflag: recv.u32(tioPtr + 3 * size),123};124}125126function native_to_wasi(tio_native: Termios): Termios {127// now translate the flags from native to WASI128const tio_wasi: Termios = {129c_iflag: 0,130c_oflag: 0,131c_cflag: 0,132c_lflag: 0,133};134let s: string[] = [];135for (const key in tio_native) {136tio_wasi[key] = 0;137for (const name of FLAGS[key]) {138if (tio_native[key] & posix.constants[name]) {139tio_wasi[key] |= constants[name];140if (log.enabled) {141s.push(name);142}143}144}145}146if (log.enabled) {147s.sort();148log("NATIVE: ", s.join(" "));149}150return tio_wasi;151}152153/*154// this doesn't end up getting used, so commented out.155function wasi_to_native(tio_wasi: Termios): Termios {156const tio_native: Termios = {157c_iflag: 0,158c_oflag: 0,159c_cflag: 0,160c_lflag: 0,161};162// let s: string[] = [];163for (const key in FLAGS) {164tio_native[key] = 0;165for (const name of FLAGS[key]) {166if (tio_wasi[key] & constants[name]) {167tio_native[key] |= posix.constants[name];168// s.push(name);169}170}171}172// s.sort();173//console.log(s.join(" "));174//require("fs").appendFileSync("/tmp/log", s + "\n");175176return tio_native;177}178*/179180return {181/*182These two functions are critical to applications that use183the terminal. They do a huge amount in a traditional POSIX system,184e.g., setting baud rates, ICANON mode, echo, etc.185186I think xterm.js is orthogonal to this; it just reflects how the187underlying terminal behaves.188189int190tcgetattr(int fildes, struct termios *termios_p);191192int193tcsetattr(int fildes, int optional_actions,194const struct termios *termios_p);195196*/197tcgetattr(wasi_fd: number, tioPtr: number): number {198const fd = wasi.FD_MAP.get(wasi_fd).real;199let tio_wasi: Termios;200let tio_native: Termios;201if (!noStdio && posix.tcgetattr != null) {202tio_native = posix.tcgetattr(fd);203// now translate the flags from native to WASI204tio_wasi = native_to_wasi(tio_native);205} else {206tio_native = {} as any; // just for logging below207if (fd == 0 || fd == 1) {208// I copied this from running it and observing.209// NO MATTER what we must c_lflag: constants.ECHO as below, though maybe210// having everything is better.211tio_wasi = {212c_iflag: 27906,213c_oflag: 5,214c_cflag: 1200,215c_lflag: 32827,216};217// // stdin - do something to avoid total disaster (see comment in header)218// tio_wasi = {219// c_iflag: 0,220// c_oflag: 0,221// c_cflag: 0,222// c_lflag: constants.ECHO, // at least this is needed or nothing will work.223// };224} else {225tio_wasi = {226c_iflag: 0,227c_oflag: 0,228c_cflag: 0,229c_lflag: 0,230};231}232}233//console.log("tcgetattr", { wasi_fd, fd, tio_wasi, tio_native });234//console.log("GET");235//wasi_to_native(tio_wasi);236termios_set(tioPtr, tio_wasi);237return 0;238},239240tcsetattr(241wasi_fd: number,242_optional_actions: number, // ignored (involves buffering and when change happens)243tioPtr: number244): number {245const fd = wasi.FD_MAP.get(wasi_fd).real;246const tio_wasi = termios_get(tioPtr);247if (noStdio || posix.tcsetattr == null || posix.tcgetattr == null) {248return 0;249}250const tio_native = posix.tcgetattr(fd);251const tio_native_orig = { ...tio_native };252const tio_wasi_current = native_to_wasi(tio_native);253254// We change in native **exactly** what they changed, leaving everything255// else the same.256let somethingChanged = false;257for (const key in FLAGS) {258for (const name of FLAGS[key]) {259if (260(tio_wasi[key] & constants[name]) !=261(tio_wasi_current[key] & constants[name])262) {263// name was changed264somethingChanged = true;265if (tio_wasi[key] & constants[name]) {266// set it267tio_native[key] |= posix.constants[name];268} else {269// unset it270tio_native[key] &= ~posix.constants[name];271}272}273}274}275if (!somethingChanged) {276log("tcsetattr: nothing changed");277return 0;278}279log("tcsetattr", { fd, tio_native, tio_native_orig });280posix.tcsetattr(fd, posix.constants.TCSANOW, tio_native);281return 0;282},283284// These are stubs for now:285286// int tcdrain(int fildes);287tcdrain(): number {288log("tcdrain - STUB");289return 0;290},291// int tcflow(int fildes, int action);292tcflow(): number {293log("tcflow - STUB");294return 0;295},296// int tcflush(int fildes, int action);297tcflush(): number {298log("tcflush - STUB");299return 0;300},301// int tcsendbreak(int fildes, int duration);302tcsendbreak(): number {303log("tcsendbreak - STUB");304return 0;305},306};307}308309310