/* Terminal */
/* Copyright (C) 2008 Emmanuel Varoquaux
This file is part of XOS.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see . */
#include "tty_struct.h"
#include "tty_queue.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define NL '\n'
#define CR '\r'
/* Utilitaires */
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);
}
/* Permissions */
/* Dit si le processus courant appartient au groupe d'avant-plan du terminal.
*/
static inline int check_group(const struct tty_struct *tty)
{
return get_pgid() == tty->pgid;
}
/* La lecture sur un terminal requiert d'appartenir au groupe de ce terminal.
*/
static inline int check_read_perm(const struct tty_struct *tty)
{
return check_group(tty);
}
/* L'ecriture sur un terminal requiert d'appartenir au groupe de ce terminal,
* sauf si TOSTOP n'est pas positionne. */
static inline int check_write_perm(const struct tty_struct *tty)
{
return !(tty->lflags & TOSTOP) || check_group(tty);
}
/* Meme condition que pour write() sauf que TOSTOP est ignore. */
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;
}
/* Les fonctions sur les files doivent toutes etre executees avec les
* interruptions masquees. */
/* output_queue */
/* Le terminal doit verifier (!tty->stopped). */
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;
}
/* Output processing */
static int process_output(struct tty_struct *tty, char c, int (*put_output_queue)(struct tty_struct *, char))
{
/* traitement de sortie */
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)
/* read_queue */
/* Tous les acces en ecriture a read_queue (push()/pop()) sont centralises dans
* cette section, qui maintient tty->read_lines et positionne
* tty->read_new_line. */
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;
}
/* Si la file est pleine, les nouveaux caracteres recus sont perdus, mais la
lecture peut tout de meme se faire de maniere a liberer de la place dans
read_queue. */
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;
}
/* stop()/start() */
/* Le terminal doit verifier (!tty->stopped). */
static void stop(struct tty_struct *tty)
{
tty->stopped = 1;
tty->vptr->do_stop(tty);
}
/* Le terminal doit verifier (tty->stopped). */
static void start(struct tty_struct *tty)
{
tty->stopped = 0;
tty->vptr->do_start(tty);
flush_output_queue(tty);
condition_signal(&tty->started_condition);
}
/* Local processing */
/* Echo d'un caractere. */
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);
}
/* Efface un caractere en sortie. */
static void erase_char(struct tty_struct *tty)
{
process_output_echo(tty, '\b');
process_output_echo(tty, ' ');
process_output_echo(tty, '\b');
}
/* Efface des caracteres en sortie de maniere a ramener le point a la position
* correspondant a l'echo de la ligne courante. */
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); ) {
/* c ne peut pas etre un delimiteur de ligne. */
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);
}
/* Efface le nombre de caracteres en sortie correspondant a l'echo du caractere
* c (mode canonique). Cette fonction est exactement l'inverse de echo(). Elle
* doit etre appelee apres avoir supprime le caractere de la ligne courante. */
static void echo_erase(struct tty_struct *tty, char c)
{
/* c ne peut pas etre un delimiteur de ligne. */
if (c == '\t')
/* C'est le seul cas ou on est oblige de recalculer le point. */
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;
/* caracteres speciaux */
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); /* effacement des caracteres */
while (del_read_queue(tty, &d));
if (tty->lflags & ECHO) {
if (!((tty->lflags & ECHOK) && (tty->lflags & ECHOKE) && (tty->lflags & ECHOE)))
echo(tty, c); /* si on n'a pas fait l'effacement des caracteres on fait echo de KILL */
if ((tty->lflags & ECHOK) && !((tty->lflags & ECHOKE) && (tty->lflags & ECHOE)))
echo(tty, NL); /* echo d'un nouvelle ligne avec ECHOK */
}
return;
}
}
/* Si la file est pleine, les nouveaux caracteres recus sont perdus. Leur
echo n'est pas fait. */
if (tty_queue_is_full(&tty->read_queue))
return;
/* ligne courante */
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;
}
/* echo */
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);
}
}
/* input_queue */
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); /* echo */
}
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);
}
/* Input processing */
static void process_input(struct tty_struct *tty, char c)
{
/* caracteres speciaux */
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);
/* conversions d'entree */
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);
}
/* Timer */
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);
}
/* Interface pour les pilotes */
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:
/* Pas de controle d'acces ici sinon il est impossible de changer le groupe
d'avant-plan du terminal ! */
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)
{
/* parametres */
tty->iflags = iflags;
tty->oflags = oflags;
tty->lflags = lflags;
memcpy(tty->cc, cc, NCCS);
tty->pgid = 0; /* pas de groupe d'avant plan */
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);
/* files */
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);
}
/* Interface systeme */
/* Lecture bloquante d'un caractere. n est le nombre de caracteres deja lus
* depuis le debut du read().
* Retourne 1 si un caractere a ete lu, 0 si aucun caractere n'a ete lu, un
* code d'erreur en cas d'erreur. */
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;
}
/* Ici read_data_available() est forcement vraie, ce qui implique que
get_read_queue() reussit forcement. */
get_read_queue(tty, c);
retval = 1;
goto end;
no_data:
retval = 0;
end:
restore_intr(intr);
return retval;
}
/* Depot bloquant d'un caractere dans la file d'ecriture. n est le nombre de
* caracteres deja ecrits depuis le debut du write().
* Retourne 1 si un caractere a ete ecrit, 0 si aucun caractere n'a ete ecrit,
* un code d'erreur en cas d'erreur. */
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;
}
/* Vidage bloquant de la file d'ecriture. */
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])
/* mode canonique : fin de fichier - le caractere est abandonne */
break;
put_fs_byte(c, fs_buf++);
n++;
count--;
if ((tty->lflags & ICANON) && is_line_delimiter(tty, c))
/* mode canonique : fin de ligne */
break;
}
while (count);
retval = n;
unlock:
mutex_unlock(&tty->read_mutex);
return retval;
}
/* Cette fonction doit pouvoir fonctionner en phase d'initialisation, lorsque
* les interruptions ne sont pas encore activees. C'est bien le cas car
* tty->stopped est toujours faux en phase d'initialisation. */
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);
}