mirror of
https://github.com/ish-app/ish.git
synced 2026-01-18 13:57:29 +00:00
426 lines
12 KiB
C
426 lines
12 KiB
C
#define DEFAULT_CHANNEL debug
|
|
#include "debug.h"
|
|
#include <string.h>
|
|
#include "kernel/calls.h"
|
|
#include "fs/poll.h"
|
|
#include "fs/tty.h"
|
|
|
|
// TODO remove magic number
|
|
struct tty_driver tty_drivers[2];
|
|
|
|
// currently supports 64 ptys
|
|
// TODO replace with hashtable
|
|
// for future reference, if you run out of ptys the error code is ENOSPC
|
|
static struct tty *ttys[2][64];
|
|
// lock this before locking a tty
|
|
static lock_t ttys_lock = LOCK_INITIALIZER;
|
|
|
|
static int tty_get(int type, int num, struct tty **tty_out) {
|
|
lock(&ttys_lock);
|
|
struct tty *tty = ttys[type][num];
|
|
if (tty == NULL) {
|
|
tty = malloc(sizeof(struct tty));
|
|
if (tty == NULL)
|
|
return _ENOMEM;
|
|
tty->refcount = 0;
|
|
tty->type = type;
|
|
tty->num = num;
|
|
list_init(&tty->fds);
|
|
// TODO default termios
|
|
memset(&tty->winsize, 0, sizeof(tty->winsize));
|
|
lock_init(&tty->lock);
|
|
lock_init(&tty->fds_lock);
|
|
pthread_cond_init(&tty->produced, NULL);
|
|
memset(tty->buf_flag, false, sizeof(tty->buf_flag));
|
|
tty->bufsize = 0;
|
|
|
|
tty->driver = &tty_drivers[type];
|
|
if (tty->driver->open) {
|
|
int err = tty->driver->open(tty);
|
|
if (err < 0) {
|
|
unlock(&ttys_lock);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
tty->session = 0;
|
|
tty->fg_group = 0;
|
|
|
|
ttys[type][num] = tty;
|
|
}
|
|
lock(&tty->lock);
|
|
tty->refcount++;
|
|
unlock(&tty->lock);
|
|
unlock(&ttys_lock);
|
|
*tty_out = tty;
|
|
return 0;
|
|
}
|
|
|
|
void tty_release(struct tty *tty) {
|
|
lock(&ttys_lock);
|
|
lock(&tty->lock);
|
|
if (--tty->refcount == 0) {
|
|
if (tty->driver->close)
|
|
tty->driver->close(tty);
|
|
ttys[tty->type][tty->num] = NULL;
|
|
unlock(&tty->lock);
|
|
free(tty);
|
|
} else {
|
|
unlock(&tty->lock);
|
|
}
|
|
unlock(&ttys_lock);
|
|
}
|
|
|
|
static int tty_open(int major, int minor, int type, struct fd *fd) {
|
|
assert(type == DEV_CHAR);
|
|
if (major == 4 && minor < 64)
|
|
type = TTY_VIRTUAL;
|
|
else if (major >= 136 && major <= 143)
|
|
type = TTY_PSEUDO;
|
|
else
|
|
assert(false);
|
|
|
|
struct tty *tty;
|
|
int err = tty_get(type, minor, &tty);
|
|
if (err < 0)
|
|
return err;
|
|
fd->tty = tty;
|
|
|
|
lock(&tty->fds_lock);
|
|
list_add(&tty->fds, &fd->other_fds);
|
|
unlock(&tty->fds_lock);
|
|
|
|
lock(&pids_lock);
|
|
if (current->sid == current->pid) {
|
|
lock(¤t->group->lock);
|
|
if (current->group->tty == NULL) {
|
|
current->group->tty = tty;
|
|
unlock(¤t->group->lock);
|
|
tty->session = current->sid;
|
|
tty->fg_group = current->pgid;
|
|
}
|
|
}
|
|
unlock(&pids_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tty_close(struct fd *fd) {
|
|
lock(&fd->tty->fds_lock);
|
|
list_remove(&fd->other_fds);
|
|
unlock(&fd->tty->fds_lock);
|
|
tty_release(fd->tty);
|
|
return 0;
|
|
}
|
|
|
|
static void tty_wake(struct tty *tty) {
|
|
notify(&tty->produced);
|
|
unlock(&tty->lock);
|
|
struct fd *fd;
|
|
lock(&tty->fds_lock);
|
|
list_for_each_entry(&tty->fds, fd, other_fds) {
|
|
poll_wake(fd);
|
|
}
|
|
unlock(&tty->fds_lock);
|
|
lock(&tty->lock);
|
|
}
|
|
|
|
static void tty_push_char(struct tty *tty, char ch, bool flag) {
|
|
if (tty->bufsize >= sizeof(tty->buf))
|
|
return;
|
|
tty->buf[tty->bufsize] = ch;
|
|
tty->buf_flag[tty->bufsize++] = flag;
|
|
}
|
|
|
|
int tty_input(struct tty *tty, const char *input, size_t size) {
|
|
lock(&tty->lock);
|
|
dword_t lflags = tty->termios.lflags;
|
|
dword_t iflags = tty->termios.iflags;
|
|
unsigned char *cc = tty->termios.cc;
|
|
|
|
#define SHOULD_ECHOCTL(ch) \
|
|
(lflags & ECHOCTL_ && \
|
|
(ch < ' ' || ch == '\x7f') && \
|
|
!(ch == '\t' || ch == '\n' || ch == cc[VSTART_] || ch == cc[VSTOP_]))
|
|
|
|
if (lflags & ISIG_) {
|
|
for (size_t i = 0; i < size; i++) {
|
|
char ch = input[i];
|
|
if (ch == cc[VINTR_]) {
|
|
input += i + 1;
|
|
size -= i + 1;
|
|
i = 0;
|
|
if (tty->fg_group != 0)
|
|
send_group_signal(tty->fg_group, SIGINT_);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (lflags & ICANON_) {
|
|
for (size_t i = 0; i < size; i++) {
|
|
char ch = input[i];
|
|
bool echo = lflags & ECHO_;
|
|
|
|
if (iflags & INLCR_ && ch == '\n')
|
|
ch = '\r';
|
|
else if (iflags & ICRNL_ && ch == '\r')
|
|
ch = '\n';
|
|
if (iflags & IGNCR_ && ch == '\r')
|
|
continue;
|
|
|
|
if (ch == cc[VERASE_] || ch == cc[VKILL_]) {
|
|
echo = lflags & ECHOK_;
|
|
int count = tty->bufsize;
|
|
if (ch == cc[VERASE_] && tty->bufsize > 0) {
|
|
echo = lflags & ECHOE_;
|
|
count = 1;
|
|
}
|
|
for (int i = 0; i < count; i++) {
|
|
// don't delete past a flag
|
|
if (tty->buf_flag[tty->bufsize - 1])
|
|
break;
|
|
tty->bufsize--;
|
|
if (echo) {
|
|
tty->driver->write(tty, "\b \b", 3);
|
|
if (SHOULD_ECHOCTL(tty->buf[tty->bufsize]))
|
|
tty->driver->write(tty, "\b \b", 3);
|
|
}
|
|
}
|
|
echo = false;
|
|
} else if (ch == cc[VEOF_]) {
|
|
ch = '\0';
|
|
goto canon_wake;
|
|
} else if (ch == '\n' || ch == cc[VEOL_]) {
|
|
// echo it now, before the read call goes through
|
|
if (echo)
|
|
tty->driver->write(tty, "\r\n", 2);
|
|
canon_wake:
|
|
tty_push_char(tty, ch, true);
|
|
echo = false;
|
|
tty_wake(tty);
|
|
} else {
|
|
tty_push_char(tty, ch, false);
|
|
}
|
|
|
|
if (echo) {
|
|
if (SHOULD_ECHOCTL(ch)) {
|
|
tty->driver->write(tty, "^", 1);
|
|
ch ^= '\100';
|
|
}
|
|
tty->driver->write(tty, &ch, 1);
|
|
}
|
|
}
|
|
} else {
|
|
if (size > sizeof(tty->buf) - 1 - tty->bufsize)
|
|
size = sizeof(tty->buf) - 1 - tty->bufsize;
|
|
memcpy(tty->buf + tty->bufsize, input, size);
|
|
tty->bufsize += size;
|
|
tty_wake(tty);
|
|
}
|
|
|
|
unlock(&tty->lock);
|
|
return 0;
|
|
}
|
|
|
|
// expects bufsize <= tty->bufsize
|
|
static void tty_read_into_buf(struct tty *tty, void *buf, size_t bufsize) {
|
|
memcpy(buf, tty->buf, bufsize);
|
|
tty->bufsize -= bufsize;
|
|
memmove(tty->buf, tty->buf + bufsize, tty->bufsize); // magic!
|
|
memmove(tty->buf_flag, tty->buf_flag + bufsize, tty->bufsize);
|
|
}
|
|
|
|
static ssize_t tty_canon_size(struct tty *tty) {
|
|
bool *flag_ptr = memchr(tty->buf_flag, true, tty->bufsize);
|
|
if (flag_ptr == NULL)
|
|
return -1;
|
|
return flag_ptr - tty->buf_flag + 1;
|
|
}
|
|
|
|
static ssize_t tty_read(struct fd *fd, void *buf, size_t bufsize) {
|
|
if (bufsize == 0)
|
|
return 0;
|
|
|
|
struct tty *tty = fd->tty;
|
|
lock(&tty->lock);
|
|
if (tty->termios.lflags & ICANON_) {
|
|
ssize_t canon_size = -1;
|
|
while ((canon_size = tty_canon_size(tty)) == -1)
|
|
wait_for(&tty->produced, &tty->lock);
|
|
// null byte means eof was typed
|
|
if (tty->buf[canon_size-1] == '\0')
|
|
canon_size--;
|
|
|
|
if (bufsize > canon_size)
|
|
bufsize = canon_size;
|
|
} else {
|
|
dword_t min = tty->termios.cc[VMIN_];
|
|
dword_t time = tty->termios.cc[VTIME_];
|
|
if (min == 0 && time == 0) {
|
|
// no need to wait for anything
|
|
} else if (min > 0 && time == 0) {
|
|
while (tty->bufsize < min)
|
|
wait_for(&tty->produced, &tty->lock);
|
|
} else {
|
|
TODO("VTIME != 0");
|
|
}
|
|
}
|
|
|
|
if (bufsize > tty->bufsize)
|
|
bufsize = tty->bufsize;
|
|
tty_read_into_buf(tty, buf, bufsize);
|
|
if (tty->buf[0] == '\0' && tty->buf_flag[0]) {
|
|
// remove the eof so the next read can succeed
|
|
char dummy;
|
|
tty_read_into_buf(tty, &dummy, 1);
|
|
}
|
|
|
|
unlock(&tty->lock);
|
|
return bufsize;
|
|
}
|
|
|
|
static ssize_t tty_write(struct fd *fd, const void *buf, size_t bufsize) {
|
|
struct tty *tty = fd->tty;
|
|
lock(&tty->lock);
|
|
dword_t oflags = tty->termios.oflags;
|
|
if (oflags & OPOST_) {
|
|
const char *cbuf = buf;
|
|
for (size_t i = 0; i < bufsize; i++) {
|
|
char ch = cbuf[i];
|
|
if (ch == '\r' && oflags & ONLRET_)
|
|
continue;
|
|
else if (ch == '\r' && oflags & OCRNL_)
|
|
ch = '\n';
|
|
else if (ch == '\n' && oflags & ONLCR_)
|
|
tty->driver->write(tty, "\r", 1);
|
|
tty->driver->write(tty, &ch, 1);
|
|
}
|
|
} else {
|
|
tty->driver->write(tty, buf, bufsize);
|
|
}
|
|
unlock(&tty->lock);
|
|
return bufsize;
|
|
}
|
|
|
|
static int tty_poll(struct fd *fd) {
|
|
struct tty *tty = fd->tty;
|
|
lock(&tty->lock);
|
|
int types = POLL_WRITE;
|
|
if (tty->termios.lflags & ICANON_) {
|
|
if (tty_canon_size(tty) != -1)
|
|
types |= POLL_READ;
|
|
} else {
|
|
if (tty->bufsize > 0)
|
|
types |= POLL_READ;
|
|
}
|
|
unlock(&tty->lock);
|
|
return types;
|
|
}
|
|
|
|
#define TCGETS_ 0x5401
|
|
#define TCSETS_ 0x5402
|
|
#define TCSETSW_ 0x5403
|
|
#define TCFLSH_ 0x540b
|
|
#define TIOCGPRGP_ 0x540f
|
|
#define TIOCSPGRP_ 0x5410
|
|
#define TIOCGWINSZ_ 0x5413
|
|
#define TIOCSWINSZ_ 0x5414
|
|
#define TCIFLUSH_ 0
|
|
#define TCOFLUSH_ 1
|
|
#define TCIOFLUSH_ 2
|
|
|
|
static ssize_t tty_ioctl_size(struct fd *fd, int cmd) {
|
|
switch (cmd) {
|
|
case TCGETS_: case TCSETS_: case TCSETSW_:
|
|
return sizeof(struct termios_);
|
|
case TCFLSH_: return 0;
|
|
case TIOCGPRGP_: case TIOCSPGRP_: return sizeof(dword_t);
|
|
case TIOCGWINSZ_: case TIOCSWINSZ_: return sizeof(struct winsize_);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static bool tty_is_current(struct tty *tty) {
|
|
lock(¤t->group->lock);
|
|
bool is_current = current->group->tty == tty;
|
|
unlock(¤t->group->lock);
|
|
return is_current;
|
|
}
|
|
|
|
static int tty_ioctl(struct fd *fd, int cmd, void *arg) {
|
|
int err = 0;
|
|
struct tty *tty = fd->tty;
|
|
lock(&tty->lock);
|
|
|
|
switch (cmd) {
|
|
case TCGETS_:
|
|
*(struct termios_ *) arg = tty->termios;
|
|
break;
|
|
case TCSETSW_:
|
|
// we have no output buffer currently
|
|
case TCSETS_:
|
|
tty->termios = *(struct termios_ *) arg;
|
|
break;
|
|
|
|
case TCFLSH_:
|
|
// only input flushing is currently useful
|
|
switch ((dword_t) arg) {
|
|
case TCIFLUSH_:
|
|
case TCIOFLUSH_:
|
|
tty->bufsize = 0;
|
|
break;
|
|
case TCOFLUSH_:
|
|
break;
|
|
default:
|
|
err = _EINVAL;
|
|
break;
|
|
};
|
|
break;
|
|
|
|
case TIOCGPRGP_:
|
|
if (!tty_is_current(tty) || tty->fg_group == 0) {
|
|
err = _ENOTTY;
|
|
break;
|
|
}
|
|
TRACELN("tty group = %d", tty->fg_group);
|
|
*(dword_t *) arg = tty->fg_group; break;
|
|
case TIOCSPGRP_:
|
|
// FIXME I think current->sid needs to be locked
|
|
if (!tty_is_current(tty) || current->sid != tty->session) {
|
|
err = _ENOTTY;
|
|
break;
|
|
}
|
|
// TODO group must be in the right session
|
|
tty->fg_group = *(dword_t *) arg;
|
|
TRACELN("tty group set to = %d", tty->fg_group);
|
|
break;
|
|
|
|
case TIOCGWINSZ_:
|
|
*(struct winsize_ *) arg = fd->tty->winsize;
|
|
break;
|
|
case TIOCSWINSZ_:
|
|
tty_set_winsize(fd->tty, *(struct winsize_ *) arg);
|
|
break;
|
|
}
|
|
|
|
unlock(&tty->lock);
|
|
return err;
|
|
}
|
|
|
|
void tty_set_winsize(struct tty *tty, struct winsize_ winsize) {
|
|
tty->winsize = winsize;
|
|
if (tty->fg_group != 0)
|
|
send_group_signal(tty->fg_group, SIGWINCH_);
|
|
}
|
|
|
|
struct dev_ops tty_dev = {
|
|
.open = tty_open,
|
|
.fd.close = tty_close,
|
|
.fd.read = tty_read,
|
|
.fd.write = tty_write,
|
|
.fd.poll = tty_poll,
|
|
.fd.ioctl_size = tty_ioctl_size,
|
|
.fd.ioctl = tty_ioctl,
|
|
};
|
|
/* struct dev_ops ptmx_dev = {.open = ptmx_open}; */
|