// SPDX-License-Identifier: GPL-2.0-or-later // // Pseudo-terminals. // // A terminal emulator, tmux, sshd and script(1) all want the same thing: a // terminal with a program on one side and *themselves* on the other, instead of // a keyboard and a screen. Unix98 spells that /dev/ptmx -- each open makes a new // pair and hands back its master -- and /dev/pts/N, the slave, which the program // gets as its stdin, stdout and controlling terminal. There were none of these, // so xterm had nothing to run a shell in and tmux could not start a server. // // Between the two sides sits a line discipline, the console's in miniature // (kernel/core/console.c): what the master writes is the slave's *input* -- // canonical editing, echo, ^C as SIGINT to the foreground group -- and what the // slave writes is its *output*, NL turned into CR NL, read back by the master. // // Every pair shares one lock. The paths are short and a second lock per pair // would only be one more thing to take in the wrong order. #include "core/types.h" #include "core/defs.h" #include "core/param.h" #include "core/spinlock.h" #include "core/sleeplock.h" #include "core/proc.h" #include "fs/fs.h" #include "fs/file.h" #include "core/uaccess.h" #include "uapi/posix.h" #include "uapi/errno.h" #include "dev/pty.h" #define PTY_BUF 4096 #define TIOCGPTN 0x80045430 #define TIOCSPTLCK 0x40045431 #define TIOCGPTPEER 0x5441 #define TIOCNOTTY 0x5422 #define TIOCGSID 0x5429 #define TIOCOUTQ 0x5411 #define TIOCPKT 0x5420 #define POLLIN_ 0x001 #define POLLOUT_ 0x004 #define POLLHUP_ 0x010 struct pty { int used; int mopen, sopen; // open file descriptions on each side int slave_seen; // the slave has been opened at least once struct termios tio; struct winsize ws; int fg_pgrp, sid; // The slave's input, after the line discipline. [r, w) is ready to read; // [w, e) is the line still being edited in canonical mode. char in[PTY_BUF]; uint ir, iw, ie; int eof; // ^D on an empty line: the next read returns 0 int abort_read; // ^C: wake the reader with EINTR // The slave's output, for the master to read. char out[PTY_BUF]; uint or, ow; }; static struct spinlock ptylock; static struct pty ptys[NPTY]; #define CTRL(x) ((x) & 0x1f) static void termios_defaults(struct termios *t) { memset(t, 0, sizeof(*t)); t->c_iflag = ICRNL | IUTF8; t->c_oflag = OPOST | ONLCR; t->c_cflag = CS8 | CREAD; t->c_lflag = ISIG | ICANON | ECHO | ECHOE | IEXTEN; t->c_cc[VINTR] = CTRL('C'); t->c_cc[VQUIT] = CTRL('\\'); t->c_cc[VERASE] = 0x7f; t->c_cc[VKILL] = CTRL('U'); t->c_cc[VEOF] = CTRL('D'); t->c_cc[VSUSP] = CTRL('Z'); t->c_cc[VWERASE]= CTRL('W'); t->c_cc[VMIN] = 1; t->c_ispeed = 15; t->c_ospeed = 15; } void pty_init(void) { initlock(&ptylock, "pty"); } int pty_alloc(void) { acquire(&ptylock); for (int i = 0; i < NPTY; i++) { struct pty *p = &ptys[i]; if (p->used) continue; memset(p, 0, sizeof(*p)); p->used = 1; termios_defaults(&p->tio); p->ws.ws_row = 24; p->ws.ws_col = 80; release(&ptylock); return i; } release(&ptylock); return -1; } int pty_used(int n) { return n >= 0 && n < NPTY && ptys[n].used; } static struct pty * pty_of(struct inode *ip) { if (ip->minor < 0 || ip->minor >= NPTY) return 0; return &ptys[ip->minor]; } // /dev/ptmx, opened: a new pair, and the master in place of the ptmx inode. // Called with ip locked, as open() holds it; hands back the master the same way. struct inode * ptmx_open(struct inode *ip) { int n = pty_alloc(); struct inode *m; if (n < 0) return 0; m = devfs_pty_master(n); if (m == 0) { ptys[n].used = 0; return 0; } iunlockput(ip); ilock(m); return m; } static void maybe_free(struct pty *p) { if (p->mopen == 0 && p->sopen == 0) p->used = 0; } // --- output: the slave writes, the master reads ------------------------------ static int out_room(struct pty *p) { return PTY_BUF - (int)(p->ow - p->or); } // One byte of output, with OPOST processing. Caller holds the lock and has made // sure there is room for two. static void out_put(struct pty *p, int c) { if ((p->tio.c_oflag & OPOST) && (p->tio.c_oflag & ONLCR) && c == '\n') p->out[p->ow++ % PTY_BUF] = '\r'; p->out[p->ow++ % PTY_BUF] = (char)c; } // Echo goes into the same queue, and is dropped rather than waited for when // the master has stopped reading: the writer here is whoever typed, and // blocking it on its own echo would deadlock a master that writes and reads // from one thread. static void echo_put(struct pty *p, int c) { if (out_room(p) < 2) return; out_put(p, c); } static void echo(struct pty *p, int c) { if (c == '\n') { if (p->tio.c_lflag & (ECHO | ECHONL)) echo_put(p, '\n'); return; } if (!(p->tio.c_lflag & ECHO)) return; if (c < 32 && c != '\t' && c != '\r') { echo_put(p, '^'); echo_put(p, c + '@'); } else { echo_put(p, c); } } static void erase_one(struct pty *p) { if (p->ie == p->iw) return; p->ie--; if ((p->tio.c_lflag & ECHO) && (p->tio.c_lflag & ECHOE)) { echo_put(p, '\b'); echo_put(p, ' '); echo_put(p, '\b'); } } // --- input: the master writes, the slave reads ------------------------------- // One byte through the line discipline. Returns a signal to send to the // foreground group, or 0. Caller holds the lock. static int input_byte(struct pty *p, int c) { struct termios *t = &p->tio; if (t->c_iflag & ISTRIP) c &= 0x7f; if (c == '\r') { if (t->c_iflag & IGNCR) return 0; if (t->c_iflag & ICRNL) c = '\n'; } else if (c == '\n' && (t->c_iflag & INLCR)) { c = '\r'; } if (t->c_lflag & ISIG) { int sig = 0; if (c == t->c_cc[VINTR]) sig = SIGINT; else if (c == t->c_cc[VQUIT]) sig = SIGQUIT; else if (c == t->c_cc[VSUSP]) sig = SIGTSTP; if (sig && c != 0) { echo(p, c); if (!(t->c_lflag & NOFLSH)) p->ie = p->iw = p->ir; p->abort_read = 1; return sig; } } if (t->c_lflag & ICANON) { if (c == t->c_cc[VERASE] || c == CTRL('H')) { erase_one(p); return 0; } if (c == t->c_cc[VKILL]) { while (p->ie != p->iw && p->in[(p->ie - 1) % PTY_BUF] != '\n') erase_one(p); return 0; } if (c == t->c_cc[VWERASE] && (t->c_lflag & IEXTEN)) { while (p->ie != p->iw && p->in[(p->ie - 1) % PTY_BUF] == ' ') erase_one(p); while (p->ie != p->iw && p->in[(p->ie - 1) % PTY_BUF] != ' ') erase_one(p); return 0; } if (c == t->c_cc[VEOF]) { if (p->ie == p->iw) p->eof = 1; p->iw = p->ie; return 0; } } if (p->ie - p->ir >= PTY_BUF) return 0; // full: dropped, as a console does p->in[p->ie++ % PTY_BUF] = (char)c; echo(p, c); if (!(t->c_lflag & ICANON) || c == '\n' || p->ie - p->ir >= PTY_BUF) p->iw = p->ie; return 0; } int ptm_write(struct inode *ip, uint64 off, char *src, int n) { struct pty *p = pty_of(ip); char kbuf[128]; int done = 0; (void)off; if (p == 0 || !p->used) return -EIO; iunlock(ip); while (done < n) { int chunk = n - done < (int)sizeof(kbuf) ? n - done : (int)sizeof(kbuf); if (copy_from_user(kbuf, (addr_t)(src + done), chunk) < 0) { ilock(ip); return done ? done : -EFAULT; } acquire(&ptylock); for (int i = 0; i < chunk; i++) { // Raw-mode input waits for room rather than vanishing: a paste into an // editor is a burst of thousands of bytes, and dropping the tail of it // is worse than making the writer wait for the reader. while (!(p->tio.c_lflag & ICANON) && p->ie - p->ir >= PTY_BUF && p->sopen > 0) { if (myproc()->killed) { release(&ptylock); ilock(ip); return done + i ? done + i : -EINTR; } sleep(&p->ie, &ptylock); } int sig = input_byte(p, kbuf[i] & 0xff); if (sig) { int pg = p->fg_pgrp; wakeup(&p->ir); release(&ptylock); if (pg > 0) sigsend_pgrp(pg, sig); acquire(&ptylock); } } wakeup(&p->ir); wakeup(&p->or); // the echo release(&ptylock); done += chunk; } poll_kick(); ilock(ip); return n; } int ptm_read(struct inode *ip, uint64 off, char *dst, int n) { struct pty *p = pty_of(ip); char kbuf[256]; int got = 0; (void)off; if (p == 0 || !p->used) return -EIO; iunlock(ip); acquire(&ptylock); while (p->or == p->ow) { // Every slave descriptor closed, after there was one: the program on the // other side has gone. Linux says EIO, and that is what terminal // emulators and tmux take as "the shell exited". if (p->slave_seen && p->sopen == 0) { release(&ptylock); ilock(ip); return -EIO; } if (myproc()->killed) { release(&ptylock); ilock(ip); return -EINTR; } sleep(&p->or, &ptylock); } while (got < n && got < (int)sizeof(kbuf) && p->or != p->ow) kbuf[got++] = p->out[p->or++ % PTY_BUF]; wakeup(&p->ow); release(&ptylock); poll_kick(); ilock(ip); if (copy_to_user((addr_t)dst, kbuf, got) < 0) return -EFAULT; return got; } int ptm_ready(struct inode *ip) { struct pty *p = pty_of(ip); return p && (p->or != p->ow || (p->slave_seen && p->sopen == 0)); } void ptm_open(struct inode *ip) { struct pty *p = pty_of(ip); if (p) { acquire(&ptylock); p->mopen++; release(&ptylock); } } // The master is gone: the slave side has been hung up on. Its readers see end // of file, its writers EIO, and its session SIGHUP -- which is how closing an // xterm window ends the shell inside it. void ptm_close(struct inode *ip) { struct pty *p = pty_of(ip); int sid = 0, pg = 0; if (p == 0) return; acquire(&ptylock); if (p->mopen > 0) p->mopen--; if (p->mopen == 0) { sid = p->sid; pg = p->fg_pgrp; wakeup(&p->ir); wakeup(&p->ow); wakeup(&p->ie); } maybe_free(p); release(&ptylock); if (sid > 0) sigsend_pgrp(sid, SIGHUP); if (pg > 0 && pg != sid) sigsend_pgrp(pg, SIGHUP); poll_kick(); } // --- the slave --------------------------------------------------------------- int pts_read(struct inode *ip, uint64 off, char *dst, int n) { struct pty *p = pty_of(ip); char kbuf[256]; int got = 0, c; (void)off; if (p == 0 || !p->used) return -EIO; if (n > (int)sizeof(kbuf)) n = sizeof(kbuf); iunlock(ip); acquire(&ptylock); while (got < n) { while (p->ir == p->iw) { if (p->eof) { p->eof = 0; goto done; } if (p->mopen == 0) goto done; // hung up: end of file // VMIN 0 is a poll, and a program that asked for one must not block. if (!(p->tio.c_lflag & ICANON) && p->tio.c_cc[VMIN] == 0) goto done; if (got > 0 && !(p->tio.c_lflag & ICANON)) goto done; if (myproc()->killed || p->abort_read) { p->abort_read = 0; release(&ptylock); ilock(ip); return got ? got : -EINTR; } if (sig_attention(myproc())) { release(&ptylock); int e = sig_blocking_check(); if (e) { ilock(ip); return got ? got : e; } acquire(&ptylock); continue; } sleep(&p->ir, &ptylock); } c = p->in[p->ir++ % PTY_BUF]; kbuf[got++] = (char)c; if ((p->tio.c_lflag & ICANON) && c == '\n') break; if (!(p->tio.c_lflag & ICANON) && got >= (p->tio.c_cc[VMIN] ? p->tio.c_cc[VMIN] : 1) && p->ir == p->iw) break; } done: wakeup(&p->ie); release(&ptylock); poll_kick(); ilock(ip); if (got && copy_to_user((addr_t)dst, kbuf, got) < 0) return -EFAULT; return got; } int pts_write(struct inode *ip, uint64 off, char *src, int n) { struct pty *p = pty_of(ip); char kbuf[128]; int done = 0; (void)off; if (p == 0 || !p->used) return -EIO; iunlock(ip); while (done < n) { int chunk = n - done < (int)sizeof(kbuf) ? n - done : (int)sizeof(kbuf); if (copy_from_user(kbuf, (addr_t)(src + done), chunk) < 0) { ilock(ip); return done ? done : -EFAULT; } acquire(&ptylock); for (int i = 0; i < chunk; i++) { while (out_room(p) < 2) { if (p->mopen == 0 || myproc()->killed) { release(&ptylock); ilock(ip); return done + i ? done + i : (p->mopen == 0 ? -EIO : -EINTR); } wakeup(&p->or); sleep(&p->ow, &ptylock); } if (p->mopen == 0) { release(&ptylock); ilock(ip); return done + i ? done + i : -EIO; } out_put(p, kbuf[i] & 0xff); } wakeup(&p->or); release(&ptylock); done += chunk; } poll_kick(); ilock(ip); return n; } int pts_ready(struct inode *ip) { struct pty *p = pty_of(ip); return p && (p->ir != p->iw || p->eof || p->mopen == 0); } // Opening a terminal makes it the caller's controlling terminal when the // caller leads a session that has none -- the System V rule, which Linux keeps // and which xterm relies on: its child calls setsid() and opens the slave, and // expects ^C to work from then on without a TIOCSCTTY. void pts_open(struct inode *ip) { struct pty *p = pty_of(ip); struct proc *me = myproc(); if (p == 0) return; acquire(&ptylock); p->sopen++; p->slave_seen = 1; if (me && me->sid == me->pid && me->ctty == 0 && p->sid == 0) { me->ctty = PTY_CTTY + ip->minor; p->sid = me->sid; p->fg_pgrp = me->pgid; } release(&ptylock); } void pts_close(struct inode *ip) { struct pty *p = pty_of(ip); if (p == 0) return; acquire(&ptylock); if (p->sopen > 0) p->sopen--; if (p->sopen == 0) { p->sid = 0; p->fg_pgrp = 0; wakeup(&p->or); // the master reads EIO now } maybe_free(p); release(&ptylock); poll_kick(); } // --- ioctl and poll ---------------------------------------------------------- int pty_ioctl(struct inode *ip, uint request, addr_t arg) { struct pty *p = pty_of(ip); int master = ip->major == PTM_MAJOR; int v; if (p == 0 || !p->used) return -EIO; switch (request) { case TIOCGPTN: if (!master) return -ENOTTY; v = ip->minor; return put_user(v, arg) < 0 ? -EFAULT : 0; case TIOCSPTLCK: case TIOCPKT: // Slaves are never locked here, so unlockpt() has nothing to undo; packet // mode is not offered, and nothing in the image asks for it. return master ? 0 : -ENOTTY; case TCGETS: { struct termios t; acquire(&ptylock); t = p->tio; release(&ptylock); return copy_to_user(arg, &t, sizeof(t)) < 0 ? -EFAULT : 0; } case TCSETS: case TCSETSW: case TCSETSF: { struct termios t; if (copy_from_user(&t, arg, sizeof(t)) < 0) return -EFAULT; acquire(&ptylock); p->tio = t; if (request == TCSETSF) p->ie = p->iw = p->ir; // Leaving canonical mode hands over the line being edited, as on the // console: a shell that switches to raw mid-line must not lose it. if (!(t.c_lflag & ICANON)) p->iw = p->ie; wakeup(&p->ir); release(&ptylock); poll_kick(); return 0; } case TCFLSH: acquire(&ptylock); if (arg == TCIFLUSH || arg == TCIOFLUSH) { p->ie = p->iw = p->ir; p->eof = 0; } if (arg == TCOFLUSH || arg == TCIOFLUSH) p->or = p->ow; wakeup(&p->ie); wakeup(&p->ow); release(&ptylock); return 0; case TIOCGWINSZ: return copy_to_user(arg, &p->ws, sizeof(p->ws)) < 0 ? -EFAULT : 0; case TIOCSWINSZ: { struct winsize ws; int changed, pg; if (copy_from_user(&ws, arg, sizeof(ws)) < 0) return -EFAULT; acquire(&ptylock); changed = ws.ws_row != p->ws.ws_row || ws.ws_col != p->ws.ws_col; p->ws = ws; pg = p->fg_pgrp; release(&ptylock); // The program inside learns of a resize the way it would on Linux. if (changed && pg > 0) sigsend_pgrp(pg, SIGWINCH); return 0; } case FIONREAD: v = master ? (int)(p->ow - p->or) : (int)(p->iw - p->ir); return put_user(v, arg) < 0 ? -EFAULT : 0; case TIOCOUTQ: v = master ? 0 : (int)(p->ow - p->or); return put_user(v, arg) < 0 ? -EFAULT : 0; case TIOCGPGRP: v = p->fg_pgrp; return put_user(v, arg) < 0 ? -EFAULT : 0; case TIOCSPGRP: if (copy_from_user(&v, arg, sizeof(v)) < 0) return -EFAULT; if (v <= 0) return -EINVAL; p->fg_pgrp = v; return 0; case TIOCGSID: if (p->sid == 0) return -ENOTTY; v = p->sid; return put_user(v, arg) < 0 ? -EFAULT : 0; case TIOCSCTTY: if (master) return -EINVAL; myproc()->ctty = PTY_CTTY + ip->minor; p->sid = myproc()->sid; p->fg_pgrp = myproc()->pgid; return 0; case TIOCNOTTY: if (myproc()->ctty == PTY_CTTY + ip->minor) myproc()->ctty = 0; return 0; } return -ENOTTY; } int pty_poll(struct inode *ip, int events, int readable, int writable) { struct pty *p = pty_of(ip); int rev = 0; if (p == 0 || !p->used) return POLLHUP_; if (ip->major == PTM_MAJOR) { if (readable && (events & POLLIN_) && p->or != p->ow) rev |= POLLIN_; if (p->slave_seen && p->sopen == 0) rev |= POLLHUP_ | (events & POLLIN_); } else { if (readable && (events & POLLIN_) && (p->ir != p->iw || p->eof)) rev |= POLLIN_; if (p->mopen == 0) rev |= POLLHUP_ | (events & POLLIN_); } if (writable && (events & POLLOUT_)) rev |= POLLOUT_; return rev; }