#include #include #include #include "dat.h" #include "fns.h" #include "linux.h" typedef struct Termios Termios; typedef struct Winsize Winsize; typedef struct Cbuf Cbuf; typedef struct Tty Tty; typedef struct Pty Pty; typedef struct Ptyfile Ptyfile; /* cflags */ enum { IGNBRK = 01, BRKINT = 02, IGNPAR = 04, PARMRK = 010, INPCK = 020, ISTRIP = 040, INLCR = 0100, IGNCR = 0200, ICRNL = 0400, IUCLC = 01000, IXON = 02000, IXANY = 04000, IXOFF = 010000, IMAXBEL = 020000, IUTF8 = 040000, }; /* oflags */ enum { OPOST = 01, OLCUC = 02, ONLCR = 04, OCRNL = 010, ONOCR = 020, ONLRET = 040, OFILL = 0100, OFDEL = 0200, NLDLY = 0400, NL0 = 0, NL1 = 0400, CRDLY = 03000, CR0 = 0, CR1 = 01000, CR2 = 02000, CR3 = 03000, TABDLY = 014000, TAB0 = 0, TAB1 = 04000, TAB2 = 010000, TAB3 = 014000, XTABS = 014000, BSDLY = 020000, BS0 = 0, BS1 = 020000, VTDLY = 040000, VT0 = 0, VT1 = 040000, FFDLY = 0100000, FF0 = 0, FF1 = 0100000, }; /* cflags */ enum { CSIZE = 060, CS5 = 0, CS6 = 020, CS7 = 040, CS8 = 060, CREAD = 0200, CLOCAL = 04000, HUPCL = 02000, }; /* lflags */ enum { ISIG = 01, ICANON = 02, XCASE = 04, ECHO = 010, ECHOE = 020, ECHOK = 040, ECHONL = 0100, NOFLSH = 0200, TOSTOP = 0400, ECHOCTL = 01000, ECHOPRT = 02000, ECHOKE = 04000, FLUSH0 = 010000, PENDIN = 040000, IEXTEN = 0100000, }; /* cc */ enum { VINTR = 0, VQUIT, VERASE, VKILL, VEOF, VTIME, VMIN, VSWTCH, VSTART, VSTOP, VSUSP, VEOL, VREPRINT, VDISCARD, VERASEW, VLNEXT, VEOL2, NCCS, }; struct Termios { int iflag; /* input modes */ int oflag; /* output modes */ int cflag; /* control modes */ int lflag; /* local modes */ uchar cline; uchar cc[NCCS]; /* control characters */ }; struct Winsize { ushort row; ushort col; ushort px; ushort py; }; struct Cbuf { int rp; int wp; char cb[256]; }; struct Tty { Termios t; Winsize winsize; int escaped; int eol; int pgid; Cbuf wb; Cbuf rb; }; struct Pty { Tty; int id; int closed; int locked; struct { Uwaitq r; Uwaitq w; } q[2]; Ref; QLock; }; struct Ptyfile { Ufile; Pty *pty; int master; }; static Pty *ptys[64]; int cbput(Cbuf *b, char c) { int x; x = b->wp+1&(sizeof(b->cb)-1); if(x == b->rp) return -1; b->cb[b->wp] = c; b->wp = x; return 0; } int cbget(Cbuf *b) { char c; if(b->rp == b->wp) return -1; c = b->cb[b->rp]; b->rp = (b->rp + 1) & (sizeof(b->cb)-1); return c; } int cbfill(Cbuf *b) { return (b->wp - b->rp) & (sizeof(b->cb)-1); } void ttyinit(Tty *t) { memset(&t->t, 0, sizeof(t->t)); t->t.iflag = ICRNL; t->t.oflag = OPOST|ONLCR|NL0|CR0|TAB0|BS0|VT0|FF0; t->t.lflag = ICANON|IEXTEN|ECHO|ECHOE|ECHOK; if(current) t->pgid = current->pgid; } int ttywrite(Tty *t, char *buf, int len) { char *p, *e; for(p=buf, e=buf+len; pt.oflag & OPOST) == 0) { if(cbput(&t->wb, c) < 0) break; continue; } switch(c) { case '\n': if(t->t.oflag & ONLCR) { if(cbput(&t->wb, '\r') < 0) goto done; } if(cbput(&t->wb, c) < 0) goto done; break; case '\t': if((t->t.oflag & TAB3) == TAB3) { int tab; tab = 8; while(tab--) cbput(&t->wb, ' '); break; } /* Fall Through */ default: if(t->t.oflag & OLCUC) if(c >= 'a' && c <= 'z') c = 'A' + (c-'a'); if(cbput(&t->wb, c) < 0) goto done; } } done: return p-buf; } int ttycanread(Tty *t, int *n) { int x; x = cbfill(&t->rb); if(t->t.lflag & ICANON){ if(t->eol == 0) return 0; } else { if(x == 0) return 0; } if(n != nil) *n = x; return 1; } int ttyread(Tty *t, char *buf, int len) { char *p, *e; if((t->t.lflag & ICANON) && t->eol == 0) return 0; for(p=buf, e=buf+len; prb)) < 0) break; if(c==0 || c=='\n'){ t->eol--; if(t->t.lflag & ICANON){ if(c == 0) break; *p++ = c; break; } } *p = c; } return p-buf; } static void echo(Tty *t, char c) { if(t->t.lflag & ECHO) { switch(c) { case '\r': if(t->t.oflag & OCRNL) { cbput(&t->wb, '\n'); break; } cbput(&t->wb, c); break; case '\n': if(t->t.oflag & ONLCR) cbput(&t->wb, '\r'); cbput(&t->wb, '\n'); break; case '\t': if((t->t.oflag & TAB3) == TAB3) { int tab; tab = 8; while(tab--) cbput(&t->wb, ' '); break; } /* Fall Through */ default: cbput(&t->wb, c); break; } } else if(c == '\n' && (t->t.lflag&(ECHONL|ICANON)) == (ECHONL|ICANON)) { if(t->t.oflag & ONLCR) cbput(&t->wb, '\r'); cbput(&t->wb, '\n'); } } static int bs(Tty *t) { char c; int x; if(cbfill(&t->rb) == 0) return 0; x = (t->rb.wp-1)&(sizeof(t->rb.cb)-1); c = t->rb.cb[x]; if(c == 0 || c == '\n') return 0; t->rb.wp = x; echo(t, '\b'); if(t->t.lflag & ECHOE) { echo(t, ' '); echo(t, '\b'); } return 1; } int ttywriteinput(Tty *t, char *buf, int len) { char *p, *e; for(p=buf, e=buf+len; pt.iflag & ISTRIP) c &= 0177; if((t->t.iflag & IXON) && c == t->t.cc[VSTOP]) { p++; break; } switch(c) { case '\r': if(t->t.iflag & IGNCR) continue; if(t->t.iflag & ICRNL) c = '\n'; break; case '\n': if(t->t.iflag&INLCR) c = '\r'; break; } if(t->t.lflag & ISIG) { if(c == t->t.cc[VINTR]){ if(t->pgid > 0) sys_kill(-t->pgid, SIGINT); continue; } if(c == t->t.cc[VQUIT]){ if(t->pgid > 0) sys_kill(-t->pgid, SIGQUIT); continue; } } if((t->t.lflag & ICANON) && t->escaped == 0) { if(c == t->t.cc[VERASE]) { bs(t); continue; } if(c == t->t.cc[VKILL]) { while(bs(t)) ; if(t->t.lflag & ECHOK) echo(t, '\n'); continue; } } if(t->escaped == 0 && (c == t->t.cc[VEOF] || c == '\n')) t->eol++; if((t->t.lflag & ICANON) == 0) { echo(t, c); cbput(&t->rb, c); continue; } if(t->escaped) echo(t, '\b'); if(c != t->t.cc[VEOF]) echo(t, c); if(c != '\\') { if(c == t->t.cc[VEOF]) c = 0; cbput(&t->rb, c); t->escaped = 0; continue; } if(t->escaped) { cbput(&t->rb, '\\'); t->escaped = 0; } else t->escaped = 1; } return p-buf; } int ttycanreadoutput(Tty *t, int *n) { int x; x = cbfill(&t->wb); if(n != nil) *n = x; return x > 0 ? 1 : 0; } int ttyreadoutput(Tty *t, char *buf, int len) { char *p, *e; for(p=buf, e=buf+len; pwb)) < 0) break; *p = c; } return p-buf; } static int pollpty(Ufile *f, void *tab) { Ptyfile *p = (Ptyfile*)f; int err; int n; if(p->pty == nil) return 0; qlock(p->pty); if(p->master){ pollwait(p, &p->pty->q[1].r, tab); n = ttycanreadoutput(p->pty, nil); } else { pollwait(p, &p->pty->q[0].r, tab); n = ttycanread(p->pty, nil); } err = POLLOUT; if(n){ err |= POLLIN; } else if(p->master==0 && p->pty->closed){ err |= (POLLIN | POLLHUP); } qunlock(p->pty); return err; } static int readpty(Ufile *f, void *data, int len, vlong) { int err; Ptyfile *p = (Ptyfile*)f; if(p->pty == nil) return -EPERM; qlock(p->pty); for(;;){ if(p->master){ err = ttycanreadoutput(p->pty, nil); } else { err = ttycanread(p->pty, nil); } if(err > 0){ if(p->master){ err = ttyreadoutput(p->pty, (char*)data, len); }else{ err = ttyread(p->pty, (char*)data, len); } } else { if(p->master == 0 && p->pty->closed){ err = -EIO; } else if(p->mode & O_NONBLOCK){ err = -EAGAIN; } else { if((err = sleepq(&p->pty->q[p->master].r, p->pty, 1)) == 0) continue; } } wakeq(&p->pty->q[!p->master].w, MAXPROC); break; } qunlock(p->pty); return err; } static int writepty(Ufile *f, void *data, int len, vlong) { Ptyfile *p = (Ptyfile*)f; int err; if(p->pty == nil) return -EPERM; if(len == 0) return len; qlock(p->pty); for(;;){ if(p->pty->closed){ err = -EIO; break; } if(p->master){ err = ttywriteinput(p->pty, (char*)data, len); } else{ err = ttywrite(p->pty, (char*)data, len); } if(err == 0){ if((err = sleepq(&p->pty->q[p->master].w, p->pty, 1)) == 0) continue; } else { if(ttycanread(p->pty, nil)) wakeq(&p->pty->q[0].r, MAXPROC); if(ttycanreadoutput(p->pty, nil)) wakeq(&p->pty->q[1].r, MAXPROC); } break; } qunlock(p->pty); return err; } static int closepty(Ufile *f) { Ptyfile *p = (Ptyfile*)f; if(p->pty == nil) return 0; qlock(p->pty); if(p->master) p->pty->closed = 1; if(!decref(p->pty)){ ptys[p->pty->id] = nil; qunlock(p->pty); free(p->pty); } else { wakeq(&p->pty->q[0].r, MAXPROC); wakeq(&p->pty->q[0].w, MAXPROC); wakeq(&p->pty->q[1].r, MAXPROC); wakeq(&p->pty->q[1].w, MAXPROC); qunlock(p->pty); } return 0; } static int changetty(Ptyfile *tty) { Ufile *old; if(old = gettty()){ putfile(old); return (old == tty) ? 0 : -EPERM; } tty->pty->pgid = current->pgid; settty(tty); return 0; } static int ioctlpty(Ufile *f, int cmd, void *arg) { Ptyfile *p = (Ptyfile*)f; int err, pid; if(p->pty == nil) return -ENOTTY; trace("ioctlpty(%s, %lux, %p)", p->path, (ulong)cmd, arg); err = 0; qlock(p->pty); switch(cmd){ default: trace("ioctlpty: unknown: 0x%x", cmd); err = -ENOTTY; break; case 0x5401: /* TCGETS */ memmove(arg, &p->pty->t, sizeof(Termios)); break; case 0x5402: /* TCSETS */ case 0x5403: /* TCSETSW */ case 0x5404: /* TCSETSF */ memmove(&p->pty->t, arg, sizeof(Termios)); break; case 0x5422: // TIOCNOTTY if((f = gettty()) && (f != p)){ putfile(f); err = -ENOTTY; break; } settty(nil); break; case 0x540E: // TIOCSCTTY err = changetty(p); break; case 0x540F: // TIOCGPGRP *(int*)arg = p->pty->pgid; break; case 0x5410: // TIOCSPGRP p->pty->pgid = *(int*)arg; break; case 0x5413: // TIOCGWINSZ memmove(arg, &p->pty->winsize, sizeof(Winsize)); break; case 0x5414: // TIOCSWINSZ if(memcmp(&p->pty->winsize, arg, sizeof(Winsize)) == 0) break; memmove(&p->pty->winsize, arg, sizeof(Winsize)); if((pid = p->pty->pgid) > 0){ qunlock(p->pty); sys_kill(-pid, SIGWINCH); return 0; } break; case 0x40045431: // TIOCSPTLCK if(p->master) p->pty->locked = *(int*)arg; break; case 0x80045430: *(int*)arg = p->pty->id; break; case 0x541B: if(arg == nil) break; if(p->master){ ttycanreadoutput(p->pty, &err); } else { ttycanread(p->pty, &err); } if(err < 0){ *((int*)arg) = 0; break; } *((int*)arg) = err; err = 0; break; } qunlock(p->pty); return err; } static int openpty(char *path, int mode, int perm, Ufile **pf) { Pty *pty; Ptyfile *p; int id; int master; USED(perm); if(strcmp("/dev/tty", path)==0){ if(*pf = gettty()) return 0; return -ENOTTY; } else if(strcmp("/dev/pts", path)==0){ pty = nil; master = -1; } else if(strcmp("/dev/ptmx", path)==0){ master = 1; for(id=0; idref = 1; ttyinit(pty); ptys[pty->id = id] = pty; } else { master = 0; if(strncmp("/dev/pts/", path, 9) != 0) return -ENOENT; id = atoi(path + 9); if(id < 0 || id >= nelem(ptys)) return -ENOENT; if((pty = ptys[id]) == nil) return -ENOENT; qlock(pty); if(pty->closed || pty->locked){ qunlock(pty); return -EIO; } incref(pty); qunlock(pty); } p = kmallocz(sizeof(*p), 1); p->dev = PTYDEV; p->ref = 1; p->fd = -1; p->mode = mode; p->path = kstrdup(path); p->pty = pty; p->master = master; if(!master && !(mode & O_NOCTTY)) changetty(p); *pf = p; return 0; } static int readdirpty(Ufile *f, Udirent **pd) { Ptyfile *p = (Ptyfile*)f; int i, n; *pd = nil; if(p->pty != nil) return -EPERM; n = 0; for(i=0; ipath, buf, S_IFCHR | 0666)) == nil) break; pd = &((*pd)->next); n++; } return n; } static int fstatpty(Ufile *f, Ustat *s) { Ptyfile *p = (Ptyfile*)f; if(p->pty != nil){ s->mode = 0666 | S_IFCHR; if(p->master){ s->rdev = 5<<8 | 2; } else { s->rdev = 3<<8; } } else { s->mode = 0777 | S_IFDIR; s->rdev = 0; } s->ino = hashpath(p->path); s->dev = 0; s->uid = current->uid; s->gid = current->gid; s->size = 0; s->atime = s->mtime = s->ctime = boottime/1000000000LL; return 0; }; static int statpty(char *path, int, Ustat *s) { if(strcmp("/dev/tty", path)==0){ s->mode = 0666 | S_IFCHR; } else if(strcmp("/dev/ptmx", path)==0){ s->mode = 0666 | S_IFCHR; s->rdev = 5<<8 | 2; } else if(strcmp("/dev/pts", path)==0){ s->mode = 0777 | S_IFDIR; } else if(strncmp("/dev/pts/", path, 9)==0){ int id; id = atoi(path + 9); if(id < 0 || id >= nelem(ptys)) return -ENOENT; if(ptys[id] == nil) return -ENOENT; s->mode = 0666 | S_IFCHR; s->rdev = 3<<8; } else { return -ENOENT; } s->ino = hashpath(path); s->uid = current->uid; s->gid = current->gid; s->size = 0; s->atime = s->mtime = s->ctime = boottime/1000000000LL; return 0; } static int chmodpty(char *path, int mode) { USED(path); USED(mode); return 0; } static int chownpty(char *path, int uid, int gid, int link) { USED(path); USED(uid); USED(gid); USED(link); return 0; } static int fchmodpty(Ufile *f, int mode) { USED(f); USED(mode); return 0; } static int fchownpty(Ufile *f, int uid, int gid) { USED(f); USED(uid); USED(gid); return 0; } static Udev ptydev = { .open = openpty, .read = readpty, .write = writepty, .poll = pollpty, .close = closepty, .readdir = readdirpty, .ioctl = ioctlpty, .fstat = fstatpty, .stat = statpty, .fchmod = fchmodpty, .fchown = fchownpty, .chmod = chmodpty, .chown = chownpty, }; void ptydevinit(void) { devtab[PTYDEV] = &ptydev; fsmount(&ptydev, "/dev/pts"); fsmount(&ptydev, "/dev/ptmx"); fsmount(&ptydev, "/dev/tty"); }