/* 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); }