#include "tty_struct.h"
#include "tty_queue.h"
#include <alarm.h>
#include <current.h>
#include <verify_area.h>
#include <proc.h>
#include <mutex.h>
#include <condition.h>
#include <segment.h>
#include <asm.h>
#include <errno.h>
#include <enums.h>
#include <string.h>
#include <ctype.h>
#include <stddef.h>
#define NL '\n'
#define CR '\r'
static inline int is_line_delimiter(const struct tty_struct *tty, char c)
{
return c == NL || c == tty->cc[VEOF] || c == tty->cc[VEOL];
}
static inline int read_data_available(const struct tty_struct *tty)
{
return tty->lflags & ICANON ? tty->read_lines || tty_queue_is_full(&tty->read_queue) : !tty_queue_is_empty(&tty->read_queue);
}
static inline int check_group(const struct tty_struct *tty)
{
return get_pgid() == tty->pgid;
}
static inline int check_read_perm(const struct tty_struct *tty)
{
return check_group(tty);
}
static inline int check_write_perm(const struct tty_struct *tty)
{
return !(tty->lflags & TOSTOP) || check_group(tty);
}
static inline int check_set_parameter_perm(const struct tty_struct *tty)
{
return check_group(tty);
}
static int get_access_read(const struct tty_struct *tty)
{
if (!check_read_perm(tty)) {
if (is_orphaned_pg(get_pgid()))
return -EIO;
signal_pg(get_pgid(), SIGTTIN);
return -ERESTARTSYS;
}
return 0;
}
static int get_access_write(const struct tty_struct *tty)
{
if (!check_write_perm(tty)) {
if (is_orphaned_pg(get_pgid()))
return -EIO;
signal_pg(get_pgid(), SIGTTOU);
return -ERESTARTSYS;
}
return 0;
}
static int get_access_set_parameter(const struct tty_struct *tty)
{
if (!check_set_parameter_perm(tty)) {
if (is_orphaned_pg(get_pgid()))
return -EIO;
signal_pg(get_pgid(), SIGTTOU);
return -ERESTARTSYS;
}
return 0;
}
static void flush_output_queue(struct tty_struct *tty)
{
tty->vptr->do_output(tty, &tty->output_queue);
}
static int put_output_queue_echo(struct tty_struct *tty, char c)
{
if (tty_queue_is_full(&tty->output_queue)) {
if (tty->stopped)
return 1;
flush_output_queue(tty);
}
tty_queue_push(&tty->output_queue, c);
return 1;
}
static int put_output_queue_write(struct tty_struct *tty, char c)
{
if (tty_queue_is_full(&tty->output_queue)) {
while (tty->stopped)
if (!condition_wait_interruptible(&tty->started_condition))
return 0;
flush_output_queue(tty);
}
tty_queue_push(&tty->output_queue, c);
return 1;
}
static int process_output(struct tty_struct *tty, char c, int (*put_output_queue)(struct tty_struct *, char))
{
if (tty->oflags & OPOST) {
switch (c) {
case NL:
if (tty->oflags & ONLCR) {
tty->output_column = 0;
if (!put_output_queue(tty, CR))
return 0;
if (!put_output_queue(tty, NL))
return 0;
return 1;
}
if (tty->oflags & ONLRET)
tty->output_column = 0;
break;
case CR:
if (tty->oflags & OCRNL) {
if (tty->oflags & ONLRET)
tty->output_column = 0;
if (!put_output_queue(tty, NL))
return 0;
return 1;
}
if ((tty->oflags & ONOCR) && tty->output_column == 0)
return 1;
tty->output_column = 0;
break;
case '\t':
tty->output_column = (tty->output_column | 7) + 1;
break;
case '\b':
if (tty->output_column > 0)
tty->output_column--;
break;
default:
if (tty->oflags & OLCUC)
c = toupper(c);
if (!iscntrl(c))
tty->output_column++;
}
}
return put_output_queue(tty, c);
}
#define process_output_echo(tty, c) process_output(tty, c, put_output_queue_echo)
#define process_output_write(tty, c) process_output(tty, c, put_output_queue_write)
static void flush_read_queue(struct tty_struct *tty)
{
tty_queue_clear(&tty->read_queue);
tty->read_lines = 0;
tty->read_new_line = 1;
}
static void put_read_queue(struct tty_struct *tty, char c)
{
if (tty_queue_push(&tty->read_queue, c))
if (is_line_delimiter(tty, c)) {
tty->read_lines++;
tty->read_new_line = 1;
}
if (read_data_available(tty))
condition_signal(&tty->read_condition);
}
static int get_read_queue(struct tty_struct *tty, char *c)
{
if (!tty_queue_pop(&tty->read_queue, c))
return 0;
if (is_line_delimiter(tty, *c))
tty->read_lines--;
if (tty_queue_is_empty(&tty->read_queue))
tty->read_new_line = 1;
return 1;
}
static int del_read_queue(struct tty_struct *tty, char *c)
{
if (!tty_queue_get_last(&tty->read_queue, c) || is_line_delimiter(tty, *c))
return 0;
tty_queue_del(&tty->read_queue);
if (tty_queue_is_empty(&tty->read_queue))
tty->read_new_line = 1;
return 1;
}
static void stop(struct tty_struct *tty)
{
tty->stopped = 1;
tty->vptr->do_stop(tty);
}
static void start(struct tty_struct *tty)
{
tty->stopped = 0;
tty->vptr->do_start(tty);
flush_output_queue(tty);
condition_signal(&tty->started_condition);
}
static void echo(struct tty_struct *tty, char c)
{
if ((tty->lflags & ECHOCTL) && iscntrl(c) && c != NL && c != '\t') {
process_output_echo(tty, '^');
process_output_echo(tty, c ^ 0100);
}
else
process_output_echo(tty, c);
}
static void erase_char(struct tty_struct *tty)
{
process_output_echo(tty, '\b');
process_output_echo(tty, ' ');
process_output_echo(tty, '\b');
}
static void backup(struct tty_struct *tty)
{
char c;
unsigned int pos;
unsigned int col;
col = tty->echo_current_first_column;
for (pos = tty->read_current_start; tty_queue_get(&tty->read_queue, &pos, &c); ) {
if (c == '\t')
col = (col | 7) + 1;
else if (iscntrl(c)) {
if (tty->lflags & ECHOCTL)
col += 2;
}
else
col++;
}
while (tty->output_column > col)
erase_char(tty);
}
static void echo_erase(struct tty_struct *tty, char c)
{
if (c == '\t')
backup(tty);
else if (iscntrl(c)) {
if (tty->lflags & ECHOCTL) {
erase_char(tty);
erase_char(tty);
}
}
else
erase_char(tty);
}
static void process_local(struct tty_struct *tty, char c)
{
char d;
if (tty->lflags & ISIG) {
if (c == tty->cc[VINTR]) {
signal_pg(tty->pgid, SIGINT);
goto flush;
}
else if (c == tty->cc[VQUIT]) {
signal_pg(tty->pgid, SIGQUIT);
goto flush;
}
else if (c == tty->cc[VSUSP]) {
if (!is_orphaned_pg(tty->pgid))
signal_pg(tty->pgid, SIGTSTP);
goto flush;
}
}
if (tty->lflags & ICANON) {
if (c == tty->cc[VERASE]) {
if (!del_read_queue(tty, &d))
return;
if ((tty->lflags & ECHO) && (tty->lflags & ECHOE))
echo_erase(tty, d);
else
echo(tty, c);
return;
}
else if (c == tty->cc[VKILL]) {
if (!del_read_queue(tty, &d))
return;
do
if ((tty->lflags & ECHO) && (tty->lflags & ECHOK) && (tty->lflags & ECHOKE) && (tty->lflags & ECHOE))
echo_erase(tty, d);
while (del_read_queue(tty, &d));
if (tty->lflags & ECHO) {
if (!((tty->lflags & ECHOK) && (tty->lflags & ECHOKE) && (tty->lflags & ECHOE)))
echo(tty, c);
if ((tty->lflags & ECHOK) && !((tty->lflags & ECHOKE) && (tty->lflags & ECHOE)))
echo(tty, NL);
}
return;
}
}
if (tty_queue_is_full(&tty->read_queue))
return;
if (tty->read_new_line) {
tty->read_current_start = tty_queue_get_end(&tty->read_queue);
tty->echo_current_first_column = tty->output_column;
tty->read_new_line = 0;
}
if (((tty->lflags & ECHO) && (!(tty->lflags & ICANON) || c != tty->cc[VEOF]))
|| ((tty->lflags & ECHONL) && (tty->lflags & ICANON) && c == NL))
echo(tty, c);
put_read_queue(tty, c);
return;
flush:
if (!(tty->lflags & NOFLSH)) {
flush_read_queue(tty);
if (!tty->stopped)
flush_output_queue(tty);
}
}
static void flush_input_queue(struct tty_struct *tty)
{
char c;
while (tty_queue_pop(&tty->input_queue, &c))
process_local(tty, c);
if (!tty->stopped)
flush_output_queue(tty);
}
static void put_input_queue(struct tty_struct *tty, char c)
{
if (tty_queue_is_full(&tty->input_queue))
flush_input_queue(tty);
tty_queue_push(&tty->input_queue, c);
}
static void process_input(struct tty_struct *tty, char c)
{
if (tty->iflags & IXON) {
if (c == tty->cc[VSTOP]) {
if (!tty->stopped)
stop(tty);
return;
}
else if (c == tty->cc[VSTART]) {
if (tty->stopped)
start(tty);
return;
}
}
if (tty->iflags & IXANY)
if (tty->stopped)
start(tty);
switch (c) {
case NL:
if (tty->iflags & INLCR)
c = CR;
break;
case CR:
if (tty->iflags & IGNCR)
return;
else if (tty->iflags & ICRNL)
c = NL;
break;
default:
if (tty->iflags & IUCLC)
c = tolower(c);
break;
}
put_input_queue(tty, c);
}
static void read_alarm_handler(struct alarm_struct *alarm)
{
struct tty_struct *tty;
tty = (void *)alarm - offsetof(struct tty_struct, read_alarm);
tty->read_timeout = 1;
condition_signal(&tty->read_condition);
}
int tty_do_ioctl(struct tty_struct *tty, int request, void *fs_arg)
{
int rv;
switch (request) {
case TGETIF:
if (!verify_area(fs_arg, sizeof (unsigned long), PF_WRITE))
return -EFAULT;
put_fs_long(tty->iflags, fs_arg);
return 0;
case TSETIF:
if ((rv = get_access_set_parameter(tty)) < 0)
return rv;
if (!verify_area(fs_arg, sizeof (unsigned long), PF_READ))
return -EFAULT;
tty->iflags = get_fs_long(fs_arg);
return 0;
case TGETOF:
if (!verify_area(fs_arg, sizeof (unsigned long), PF_WRITE))
return -EFAULT;
put_fs_long(tty->oflags, fs_arg);
return 0;
case TSETOF:
if ((rv = get_access_set_parameter(tty)) < 0)
return rv;
if (!verify_area(fs_arg, sizeof (unsigned long), PF_READ))
return -EFAULT;
tty->oflags = get_fs_long(fs_arg);
return 0;
case TGETLF:
if (!verify_area(fs_arg, sizeof (unsigned long), PF_WRITE))
return -EFAULT;
put_fs_long(tty->lflags, fs_arg);
return 0;
case TSETLF:
if ((rv = get_access_set_parameter(tty)) < 0)
return rv;
if (!verify_area(fs_arg, sizeof (unsigned long), PF_READ))
return -EFAULT;
tty->lflags = get_fs_long(fs_arg);
return 0;
case TGETCC:
if (!verify_area(fs_arg, NCCS, PF_WRITE))
return -EFAULT;
memcpy_tofs(fs_arg, tty->cc, NCCS);
return 0;
case TSETCC:
if ((rv = get_access_set_parameter(tty)) < 0)
return rv;
if (!verify_area(fs_arg, NCCS, PF_READ))
return -EFAULT;
memcpy_fromfs(tty->cc, fs_arg, NCCS);
return 0;
case TGETPGID:
if (!verify_area(fs_arg, sizeof (int), PF_WRITE))
return -EFAULT;
put_fs_long(tty->pgid, fs_arg);
return 0;
case TSETPGID:
if (!verify_area(fs_arg, sizeof (int), PF_READ))
return -EFAULT;
tty->pgid = get_fs_long(fs_arg);
return 0;
default:
return -EINVAL;
}
}
void tty_init(struct tty_struct *tty, unsigned long iflags, unsigned long oflags, unsigned long lflags, const unsigned char cc[], unsigned int column)
{
tty->iflags = iflags;
tty->oflags = oflags;
tty->lflags = lflags;
memcpy(tty->cc, cc, NCCS);
tty->pgid = 0;
tty->stopped = 0;
tty->output_column = column;
tty->read_lines = 0;
tty->read_new_line = 1;
tty->read_timeout = 0;
alarm_init(&tty->read_alarm, read_alarm_handler);
condition_init(&tty->started_condition);
condition_init(&tty->read_condition);
mutex_init(&tty->read_mutex);
mutex_init(&tty->write_mutex);
tty_queue_init(&tty->input_queue);
tty_queue_init(&tty->read_queue);
tty_queue_init(&tty->output_queue);
}
void tty_put(struct tty_struct *tty, char c)
{
int intr;
disable_intr(intr);
process_input(tty, c);
restore_intr(intr);
}
void tty_process_input_queue(struct tty_struct *tty)
{
int intr;
disable_intr(intr);
flush_input_queue(tty);
restore_intr(intr);
}
void tty_stop(struct tty_struct *tty)
{
int intr;
disable_intr(intr);
if (tty->stopped)
goto end;
stop(tty);
end:
restore_intr(intr);
}
void tty_start(struct tty_struct *tty)
{
int intr;
disable_intr(intr);
if (!tty->stopped)
goto end;
start(tty);
end:
restore_intr(intr);
}
static int read_char(struct tty_struct *tty, unsigned int n, char *c)
{
int intr;
int retval;
disable_intr(intr);
while (!read_data_available(tty)) {
if (tty->lflags & ICANON)
goto no_timer;
else {
if (tty->cc[VMIN]) {
if (n >= tty->cc[VMIN])
goto no_data;
else {
if (tty->cc[VTIME] && n)
goto timer;
else
goto no_timer;
}
}
else {
if (tty->cc[VTIME] && !n)
goto timer;
else
goto no_data;
}
}
timer:
alarm_set(&tty->read_alarm, tty->cc[VTIME] * 100);
goto wait;
no_timer:
alarm_unset(&tty->read_alarm);
wait:
tty->read_timeout = 0;
if (!condition_wait_interruptible(&tty->read_condition)) {
if (!n) {
retval = -ERESTARTSYS;
goto end;
}
goto no_data;
}
if (tty->read_timeout)
goto no_data;
}
get_read_queue(tty, c);
retval = 1;
goto end;
no_data:
retval = 0;
end:
restore_intr(intr);
return retval;
}
static int put_write_char(struct tty_struct *tty, unsigned int n, char c)
{
int retval;
int intr;
disable_intr(intr);
if (!process_output_write(tty, c)) {
if (!n) {
retval = -ERESTARTSYS;
goto end;
}
retval = 0;
goto end;
}
retval = 1;
end:
restore_intr(intr);
return retval;
}
static int flush_write(struct tty_struct *tty)
{
int retval;
int intr;
disable_intr(intr);
while (tty->stopped) {
if (!tty_queue_is_full(&tty->output_queue)) {
retval = 1;
goto end;
}
if (!(retval = condition_wait_interruptible(&tty->started_condition)))
goto end;
}
flush_output_queue(tty);
retval = 1;
end:
restore_intr(intr);
return retval;
}
int tty_read(struct tty_struct *tty, char *fs_buf, unsigned int count)
{
int retval;
unsigned int n;
char c;
if ((retval = get_access_read(tty)) < 0)
return retval;
if (!count)
return 0;
if (!verify_area(fs_buf, count, PF_WRITE))
return -EFAULT;
if (!mutex_lock(&tty->read_mutex))
return -ERESTARTSYS;
n = 0;
do {
if ((retval = read_char(tty, n, &c)) < 0)
goto unlock;
if (!retval)
break;
if ((tty->lflags & ICANON) && c == tty->cc[VEOF])
break;
put_fs_byte(c, fs_buf++);
n++;
count--;
if ((tty->lflags & ICANON) && is_line_delimiter(tty, c))
break;
}
while (count);
retval = n;
unlock:
mutex_unlock(&tty->read_mutex);
return retval;
}
int tty_write(struct tty_struct *tty, const char *fs_buf, unsigned int count)
{
int retval;
unsigned int n;
if ((retval = get_access_write(tty)) < 0)
return retval;
if (!count)
return 0;
if (!verify_area(fs_buf, count, PF_READ))
return -EFAULT;
if (!mutex_lock(&tty->write_mutex))
return -ERESTARTSYS;
n = 0;
do {
if ((retval = put_write_char(tty, n, get_fs_byte(fs_buf++))) < 0)
goto unlock;
if (!retval)
break;
n++;
count--;
}
while (count);
if (n)
if ((retval = flush_write(tty)) < 0)
goto unlock;
retval = n;
unlock:
mutex_unlock(&tty->write_mutex);
return retval;
}
int tty_ioctl(struct tty_struct *tty, int request, void *fs_arg)
{
return tty->vptr->do_ioctl(tty, request, fs_arg);
}