/* * linux/kernel/tty_io.c * * Copyright (C) 1991, 1992 Linus Torvalds */ /* * 'tty_io.c' gives an orthogonal feeling to tty's, be they consoles * or rs-channels. It also implements echoing, cooked mode etc. * * Kill-line thanks to John T Kohl, who also corrected VMIN = VTIME = 0. * * Modified by Theodore Ts'o, 9/14/92, to dynamically allocate the * tty_struct and tty_queue structures. Previously there was a array * of 256 tty_struct's which was statically allocated, and the * tty_queue structures were allocated at boot time. Both are now * dynamically allocated only when the tty is open. * * Also restructured routines so that there is more of a separation * between the high-level tty routines (tty_io.c and tty_ioctl.c) and * the low-level tty routines (serial.c, pty.c, console.c). This * makes for cleaner and more compact code. -TYT, 9/17/92 * * Modified by Fred N. van Kempen, 01/29/93, to add line disciplines * which can be dynamically activated and de-activated by the line * discipline handling modules (like SLIP). * * NOTE: pay no attention to the line discpline code (yet); its * interface is still subject to change in this version... * -- TYT, 1/31/92 * * Added functionality to the OPOST tty handling. No delays, but all * other bits should be there. * -- Nick Holloway , 27th May 1993. * * Rewrote canonical mode and added more termios flags. * -- julian@uhunix.uhcc.hawaii.edu (J. Cowley), 13Jan94 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kbd_kern.h" #include "vt_kern.h" #define CONSOLE_DEV MKDEV(TTY_MAJOR,0) #define MAX_TTYS 256 struct tty_struct *tty_table[MAX_TTYS]; struct termios *tty_termios[MAX_TTYS]; /* We need to keep the termios state */ /* around, even when a tty is closed */ struct termios *termios_locked[MAX_TTYS]; /* Bitfield of locked termios flags*/ struct tty_ldisc ldiscs[NR_LDISCS]; /* line disc dispatch table */ int tty_check_write[MAX_TTYS/32]; /* bitfield for the bh handler */ /* * fg_console is the current virtual console, * redirect is the pseudo-tty that console output * is redirected to if asked by TIOCCONS. */ int fg_console = 0; struct tty_struct * redirect = NULL; struct wait_queue * keypress_wait = NULL; static void initialize_tty_struct(int line, struct tty_struct *tty); static void initialize_termios(int line, struct termios *tp); static int tty_read(struct inode *, struct file *, char *, int); static int tty_write(struct inode *, struct file *, char *, int); static int tty_select(struct inode *, struct file *, int, select_table *); static int tty_open(struct inode *, struct file *); static void tty_release(struct inode *, struct file *); int tty_register_ldisc(int disc, struct tty_ldisc *new_ldisc) { if (disc < N_TTY || disc >= NR_LDISCS) return -EINVAL; if (new_ldisc) { ldiscs[disc] = *new_ldisc; ldiscs[disc].flags |= LDISC_FLAG_DEFINED; } else memset(&ldiscs[disc], 0, sizeof(struct tty_ldisc)); return 0; } void put_tty_queue(unsigned char c, struct tty_queue * queue) { int head; unsigned long flags; save_flags(flags); cli(); head = (queue->head + 1) & (TTY_BUF_SIZE-1); if (head != queue->tail) { queue->buf[queue->head] = c; queue->head = head; } restore_flags(flags); } int get_tty_queue(struct tty_queue * queue) { int result = -1; unsigned long flags; save_flags(flags); cli(); if (queue->tail != queue->head) { result = queue->buf[queue->tail]; INC(queue->tail); } restore_flags(flags); return result; } /* * This routine copies out a maximum of buflen characters from the * read_q; it is a convenience for line disciplines so they can grab a * large block of data without calling get_tty_char directly. It * returns the number of characters actually read. Return terminates * if an error character is read from the queue and the return value * is negated. */ int tty_read_raw_data(struct tty_struct *tty, unsigned char *bufp, int buflen) { int result = 0; unsigned char *p = bufp; unsigned long flags; int head, tail; int ok = 1; save_flags(flags); cli(); tail = tty->read_q.tail; head = tty->read_q.head; while ((result < buflen) && (tail!=head) && ok) { ok = !clear_bit (tail, &tty->readq_flags); *p++ = tty->read_q.buf[tail++]; tail &= TTY_BUF_SIZE-1; result++; } tty->read_q.tail = tail; restore_flags(flags); return (ok) ? result : -result; } void tty_write_flush(struct tty_struct * tty) { if (!tty->write || EMPTY(&tty->write_q)) return; if (set_bit(TTY_WRITE_BUSY,&tty->flags)) return; tty->write(tty); if (!clear_bit(TTY_WRITE_BUSY,&tty->flags)) printk("tty_write_flush: bit already cleared\n"); } void tty_read_flush(struct tty_struct * tty) { if (!tty || EMPTY(&tty->read_q)) return; if (set_bit(TTY_READ_BUSY, &tty->flags)) return; ldiscs[tty->disc].handler(tty); if (!clear_bit(TTY_READ_BUSY, &tty->flags)) printk("tty_read_flush: bit already cleared\n"); } static int hung_up_tty_read(struct inode * inode, struct file * file, char * buf, int count) { return 0; } static int hung_up_tty_write(struct inode * inode, struct file * file, char * buf, int count) { return -EIO; } static int hung_up_tty_select(struct inode * inode, struct file * filp, int sel_type, select_table * wait) { return 1; } static int hung_up_tty_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg) { return -EIO; } static int tty_lseek(struct inode * inode, struct file * file, off_t offset, int orig) { return -ESPIPE; } static struct file_operations tty_fops = { tty_lseek, tty_read, tty_write, NULL, /* tty_readdir */ tty_select, tty_ioctl, NULL, /* tty_mmap */ tty_open, tty_release }; static struct file_operations hung_up_tty_fops = { tty_lseek, hung_up_tty_read, hung_up_tty_write, NULL, /* hung_up_tty_readdir */ hung_up_tty_select, hung_up_tty_ioctl, NULL, /* hung_up_tty_mmap */ NULL, /* hung_up_tty_open */ tty_release /* hung_up_tty_release */ }; void do_tty_hangup(struct tty_struct * tty, struct file_operations *fops) { int i; struct file * filp; struct task_struct *p; int dev; if (!tty) return; dev = MKDEV(TTY_MAJOR,tty->line); for (filp = first_file, i=0; if_next) { if (!filp->f_count) continue; if (filp->f_rdev != dev) continue; if (filp->f_inode && filp->f_inode->i_rdev == CONSOLE_DEV) continue; if (filp->f_op != &tty_fops) continue; filp->f_op = fops; } flush_input(tty); flush_output(tty); wake_up_interruptible(&tty->secondary.proc_list); if (tty->session > 0) kill_sl(tty->session,SIGHUP,1); tty->session = 0; tty->pgrp = -1; for_each_task(p) { if (p->tty == tty->line) p->tty = -1; } if (tty->hangup) (tty->hangup)(tty); } void tty_hangup(struct tty_struct * tty) { #ifdef TTY_DEBUG_HANGUP printk("tty%d hangup...\n", tty->line); #endif do_tty_hangup(tty, &hung_up_tty_fops); } void tty_vhangup(struct tty_struct * tty) { #ifdef TTY_DEBUG_HANGUP printk("tty%d vhangup...\n", tty->line); #endif do_tty_hangup(tty, &hung_up_tty_fops); } int tty_hung_up_p(struct file * filp) { return (filp->f_op == &hung_up_tty_fops); } /* * This function is typically called only by the session leader, when * it wants to dissassociate itself from its controlling tty. * * It performs the following functions: * (1) Sends a SIGHUP to the foreground process group * (2) Clears the tty from being controlling the session * (3) Clears the controlling tty for all processes in the * session group. */ void disassociate_ctty(int priv) { struct tty_struct *tty; struct task_struct *p; if (current->tty >= 0) { tty = tty_table[current->tty]; if (tty) { if (tty->pgrp > 0) kill_pg(tty->pgrp, SIGHUP, priv); tty->session = 0; tty->pgrp = -1; } else printk("disassociate_ctty: ctty is NULL?!?"); } for_each_task(p) if (p->session == current->session) p->tty = -1; } /* * Sometimes we want to wait until a particular VT has been activated. We * do it in a very simple manner. Everybody waits on a single queue and * get woken up at once. Those that are satisfied go on with their business, * while those not ready go back to sleep. Seems overkill to add a wait * to each vt just for this - usually this does nothing! */ static struct wait_queue *vt_activate_queue = NULL; /* * Sleeps until a vt is activated, or the task is interrupted. Returns * 0 if activation, -1 if interrupted. */ int vt_waitactive(void) { interruptible_sleep_on(&vt_activate_queue); return (current->signal & ~current->blocked) ? -1 : 0; } #define vt_wake_waitactive() wake_up(&vt_activate_queue) extern int kill_proc(int pid, int sig, int priv); /* * Performs the back end of a vt switch */ void complete_change_console(unsigned int new_console) { unsigned char old_vc_mode; if (new_console == fg_console || new_console >= NR_CONSOLES) return; /* * If we're switching, we could be going from KD_GRAPHICS to * KD_TEXT mode or vice versa, which means we need to blank or * unblank the screen later. */ old_vc_mode = vt_cons[fg_console].vc_mode; update_screen(new_console); /* * If this new console is under process control, send it a signal * telling it that it has acquired. Also check if it has died and * clean up (similar to logic employed in change_console()) */ if (vt_cons[new_console].vt_mode.mode == VT_PROCESS) { /* * Send the signal as privileged - kill_proc() will * tell us if the process has gone or something else * is awry */ if (kill_proc(vt_cons[new_console].vt_pid, vt_cons[new_console].vt_mode.acqsig, 1) != 0) { /* * The controlling process has died, so we revert back to * normal operation. In this case, we'll also change back * to KD_TEXT mode. I'm not sure if this is strictly correct * but it saves the agony when the X server dies and the screen * remains blanked due to KD_GRAPHICS! It would be nice to do * this outside of VT_PROCESS but there is no single process * to account for and tracking tty count may be undesirable. */ vt_cons[new_console].vc_mode = KD_TEXT; clr_vc_kbd_mode(kbd_table + new_console, VC_RAW); clr_vc_kbd_mode(kbd_table + new_console, VC_MEDIUMRAW); vt_cons[new_console].vt_mode.mode = VT_AUTO; vt_cons[new_console].vt_mode.waitv = 0; vt_cons[new_console].vt_mode.relsig = 0; vt_cons[new_console].vt_mode.acqsig = 0; vt_cons[new_console].vt_mode.frsig = 0; vt_cons[new_console].vt_pid = -1; vt_cons[new_console].vt_newvt = -1; } } /* * We do this here because the controlling process above may have * gone, and so there is now a new vc_mode */ if (old_vc_mode != vt_cons[new_console].vc_mode) { if (vt_cons[new_console].vc_mode == KD_TEXT) unblank_screen(); else { timer_active &= ~(1<= NR_CONSOLES) return; /* * If this vt is in process mode, then we need to handshake with * that process before switching. Essentially, we store where that * vt wants to switch to and wait for it to tell us when it's done * (via VT_RELDISP ioctl). * * We also check to see if the controlling process still exists. * If it doesn't, we reset this vt to auto mode and continue. * This is a cheap way to track process control. The worst thing * that can happen is: we send a signal to a process, it dies, and * the switch gets "lost" waiting for a response; hopefully, the * user will try again, we'll detect the process is gone (unless * the user waits just the right amount of time :-) and revert the * vt to auto control. */ if (vt_cons[fg_console].vt_mode.mode == VT_PROCESS) { /* * Send the signal as privileged - kill_proc() will * tell us if the process has gone or something else * is awry */ if (kill_proc(vt_cons[fg_console].vt_pid, vt_cons[fg_console].vt_mode.relsig, 1) == 0) { /* * It worked. Mark the vt to switch to and * return. The process needs to send us a * VT_RELDISP ioctl to complete the switch. */ vt_cons[fg_console].vt_newvt = new_console; return; } /* * The controlling process has died, so we revert back to * normal operation. In this case, we'll also change back * to KD_TEXT mode. I'm not sure if this is strictly correct * but it saves the agony when the X server dies and the screen * remains blanked due to KD_GRAPHICS! It would be nice to do * this outside of VT_PROCESS but there is no single process * to account for and tracking tty count may be undesirable. */ vt_cons[fg_console].vc_mode = KD_TEXT; clr_vc_kbd_mode(kbd_table + fg_console, VC_RAW); clr_vc_kbd_mode(kbd_table + fg_console, VC_MEDIUMRAW); vt_cons[fg_console].vt_mode.mode = VT_AUTO; vt_cons[fg_console].vt_mode.waitv = 0; vt_cons[fg_console].vt_mode.relsig = 0; vt_cons[fg_console].vt_mode.acqsig = 0; vt_cons[fg_console].vt_mode.frsig = 0; vt_cons[fg_console].vt_pid = -1; vt_cons[fg_console].vt_newvt = -1; /* * Fall through to normal (VT_AUTO) handling of the switch... */ } /* * Ignore all switches in KD_GRAPHICS+VT_AUTO mode */ if (vt_cons[fg_console].vc_mode == KD_GRAPHICS) return; complete_change_console(new_console); } void wait_for_keypress(void) { sleep_on(&keypress_wait); } void stop_tty(struct tty_struct *tty) { if (tty->stopped) return; tty->stopped = 1; if (tty->link && tty->link->packet) { tty->ctrl_status &= ~TIOCPKT_START; tty->ctrl_status |= TIOCPKT_STOP; wake_up_interruptible(&tty->link->secondary.proc_list); } if (tty->stop) (tty->stop)(tty); if (IS_A_CONSOLE(tty->line)) { set_vc_kbd_led(kbd_table + fg_console, VC_SCROLLOCK); set_leds(); } } void start_tty(struct tty_struct *tty) { if (!tty->stopped) return; tty->stopped = 0; if (tty->link && tty->link->packet) { tty->ctrl_status &= ~TIOCPKT_STOP; tty->ctrl_status |= TIOCPKT_START; wake_up_interruptible(&tty->link->secondary.proc_list); } if (tty->start) (tty->start)(tty); TTY_WRITE_FLUSH(tty); if (IS_A_CONSOLE(tty->line)) { clr_vc_kbd_led(kbd_table + fg_console, VC_SCROLLOCK); set_leds(); } } /* Perform OPOST processing. Returns -1 when the write_q becomes full and the character must be retried. */ static int opost(unsigned char c, struct tty_struct *tty) { if (FULL(&tty->write_q)) return -1; if (O_OPOST(tty)) { switch (c) { case '\n': if (O_ONLRET(tty)) tty->column = 0; if (O_ONLCR(tty)) { if (LEFT(&tty->write_q) < 2) return -1; put_tty_queue('\r', &tty->write_q); tty->column = 0; } tty->canon_column = tty->column; break; case '\r': if (O_ONOCR(tty) && tty->column == 0) return 0; if (O_OCRNL(tty)) { c = '\n'; if (O_ONLRET(tty)) tty->canon_column = tty->column = 0; break; } tty->canon_column = tty->column = 0; break; case '\t': if (O_TABDLY(tty) == XTABS) { if (LEFT(&tty->write_q) < 8) return -1; do put_tty_queue(' ', &tty->write_q); while (++tty->column % 8); return 0; } tty->column = (tty->column | 7) + 1; break; case '\b': if (tty->column > 0) tty->column--; break; default: if (O_OLCUC(tty)) c = toupper(c); if (!iscntrl(c)) tty->column++; break; } } put_tty_queue(c, &tty->write_q); return 0; } /* Must be called only when L_ECHO(tty) is true. */ static void echo_char(unsigned char c, struct tty_struct *tty) { if (L_ECHOCTL(tty) && iscntrl(c) && c != '\t') { opost('^', tty); opost(c ^ 0100, tty); } else opost(c, tty); } static void eraser(unsigned char c, struct tty_struct *tty) { enum { ERASE, WERASE, KILL } kill_type; int seen_alnums; if (tty->secondary.head == tty->canon_head) { /* opost('\a', tty); */ /* what do you think? */ return; } if (c == ERASE_CHAR(tty)) kill_type = ERASE; else if (c == WERASE_CHAR(tty)) kill_type = WERASE; else { if (!L_ECHO(tty)) { tty->secondary.head = tty->canon_head; return; } if (!L_ECHOK(tty) || !L_ECHOKE(tty)) { tty->secondary.head = tty->canon_head; if (tty->erasing) { opost('/', tty); tty->erasing = 0; } echo_char(KILL_CHAR(tty), tty); /* Add a newline if ECHOK is on and ECHOKE is off. */ if (L_ECHOK(tty)) opost('\n', tty); return; } kill_type = KILL; } seen_alnums = 0; while (tty->secondary.head != tty->canon_head) { c = LAST(&tty->secondary); if (kill_type == WERASE) { /* Equivalent to BSD's ALTWERASE. */ if (isalnum(c) || c == '_') seen_alnums++; else if (seen_alnums) break; } DEC(tty->secondary.head); if (L_ECHO(tty)) { if (L_ECHOPRT(tty)) { if (!tty->erasing) { opost('\\', tty); tty->erasing = 1; } echo_char(c, tty); } else if (!L_ECHOE(tty)) { echo_char(ERASE_CHAR(tty), tty); } else if (c == '\t') { unsigned int col = tty->canon_column; unsigned long tail = tty->canon_head; /* Find the column of the last char. */ while (tail != tty->secondary.head) { c = tty->secondary.buf[tail]; if (c == '\t') col = (col | 7) + 1; else if (iscntrl(c)) { if (L_ECHOCTL(tty)) col += 2; } else col++; INC(tail); } /* Now backup to that column. */ while (tty->column > col) { /* Can't use opost here. */ put_tty_queue('\b', &tty->write_q); tty->column--; } } else { if (iscntrl(c) && L_ECHOCTL(tty)) { opost('\b', tty); opost(' ', tty); opost('\b', tty); } if (!iscntrl(c) || L_ECHOCTL(tty)) { opost('\b', tty); opost(' ', tty); opost('\b', tty); } } } if (kill_type == ERASE) break; } if (tty->erasing && tty->secondary.head == tty->canon_head) { opost('/', tty); tty->erasing = 0; } } static void isig(int sig, struct tty_struct *tty) { kill_pg(tty->pgrp, sig, 1); if (!L_NOFLSH(tty)) { flush_input(tty); flush_output(tty); } } static void copy_to_cooked(struct tty_struct * tty) { int c, special_flag; unsigned long flags; if (!tty) { printk("copy_to_cooked: called with NULL tty\n"); return; } if (!tty->write) { printk("copy_to_cooked: tty %d has null write routine\n", tty->line); } while (1) { /* * Check to see how much room we have left in the * secondary queue. Send a throttle command or abort * if necessary. */ c = LEFT(&tty->secondary); if (tty->throttle && (c < SQ_THRESHOLD_LW) && !set_bit(TTY_SQ_THROTTLED, &tty->flags)) tty->throttle(tty, TTY_THROTTLE_SQ_FULL); if (c == 0) break; save_flags(flags); cli(); if (!EMPTY(&tty->read_q)) { c = tty->read_q.buf[tty->read_q.tail]; special_flag = clear_bit(tty->read_q.tail, &tty->readq_flags); INC(tty->read_q.tail); restore_flags(flags); } else { restore_flags(flags); break; } if (special_flag) { tty->char_error = c; continue; } if (tty->char_error) { if (tty->char_error == TTY_BREAK) { tty->char_error = 0; if (I_IGNBRK(tty)) continue; /* A break is handled by the lower levels. */ if (I_BRKINT(tty)) continue; if (I_PARMRK(tty)) { put_tty_queue('\377', &tty->secondary); put_tty_queue('\0', &tty->secondary); } put_tty_queue('\0', &tty->secondary); continue; } if (tty->char_error == TTY_OVERRUN) { tty->char_error = 0; printk("tty%d: input overrun\n", tty->line); continue; } /* Must be a parity or frame error */ tty->char_error = 0; if (I_IGNPAR(tty)) { continue; } if (I_PARMRK(tty)) { put_tty_queue('\377', &tty->secondary); put_tty_queue('\0', &tty->secondary); put_tty_queue(c, &tty->secondary); } else put_tty_queue('\0', &tty->secondary); continue; } if (I_ISTRIP(tty)) c &= 0x7f; if (!tty->lnext) { if (c == '\r') { if (I_IGNCR(tty)) continue; if (I_ICRNL(tty)) c = '\n'; } else if (c == '\n' && I_INLCR(tty)) c = '\r'; } if (I_IUCLC(tty) && L_IEXTEN(tty)) c=tolower(c); if (c == __DISABLED_CHAR) tty->lnext = 1; if (L_ICANON(tty) && !tty->lnext) { if (c == ERASE_CHAR(tty) || c == KILL_CHAR(tty) || (c == WERASE_CHAR(tty) && L_IEXTEN(tty))) { eraser(c, tty); continue; } if (c == LNEXT_CHAR(tty) && L_IEXTEN(tty)) { tty->lnext = 1; if (L_ECHO(tty)) { if (tty->erasing) { opost('/', tty); tty->erasing = 0; } if (L_ECHOCTL(tty)) { opost('^', tty); opost('\b', tty); } } continue; } if (c == REPRINT_CHAR(tty) && L_ECHO(tty) && L_IEXTEN(tty)) { unsigned long tail = tty->canon_head; if (tty->erasing) { opost('/', tty); tty->erasing = 0; } echo_char(c, tty); opost('\n', tty); while (tail != tty->secondary.head) { echo_char(tty->secondary.buf[tail], tty); INC(tail); } continue; } } if (I_IXON(tty) && !tty->lnext) { if ((tty->stopped && I_IXANY(tty) && L_IEXTEN(tty)) || c == START_CHAR(tty)) { start_tty(tty); continue; } if (c == STOP_CHAR(tty)) { stop_tty(tty); continue; } } if (L_ISIG(tty) && !tty->lnext) { if (c == INTR_CHAR(tty)) { isig(SIGINT, tty); continue; } if (c == QUIT_CHAR(tty)) { isig(SIGQUIT, tty); continue; } if (c == SUSP_CHAR(tty)) { if (!is_orphaned_pgrp(tty->pgrp)) isig(SIGTSTP, tty); continue; } } if (tty->erasing) { opost('/', tty); tty->erasing = 0; } if (c == '\n' && !tty->lnext) { if (L_ECHO(tty) || (L_ICANON(tty) && L_ECHONL(tty))) opost('\n', tty); } else if (L_ECHO(tty)) { /* Don't echo the EOF char in canonical mode. Sun handles this differently by echoing the char and then backspacing, but that's a hack. */ if (c != EOF_CHAR(tty) || !L_ICANON(tty) || tty->lnext) { /* Record the column of first canon char. */ if (tty->canon_head == tty->secondary.head) tty->canon_column = tty->column; echo_char(c, tty); } } if (I_PARMRK(tty) && c == (unsigned char) '\377' && (c != EOF_CHAR(tty) || !L_ICANON(tty) || tty->lnext)) put_tty_queue(c, &tty->secondary); if (L_ICANON(tty) && !tty->lnext && (c == '\n' || c == EOF_CHAR(tty) || c == EOL_CHAR(tty) || (c == EOL2_CHAR(tty) && L_IEXTEN(tty)))) { if (c == EOF_CHAR(tty)) c = __DISABLED_CHAR; set_bit(tty->secondary.head, &tty->secondary_flags); put_tty_queue(c, &tty->secondary); tty->canon_head = tty->secondary.head; tty->canon_data++; } else put_tty_queue(c, &tty->secondary); tty->lnext = 0; } if (!EMPTY(&tty->write_q)) TTY_WRITE_FLUSH(tty); if (L_ICANON(tty) ? tty->canon_data : !EMPTY(&tty->secondary)) wake_up_interruptible(&tty->secondary.proc_list); if (tty->throttle && (LEFT(&tty->read_q) >= RQ_THRESHOLD_HW) && clear_bit(TTY_RQ_THROTTLED, &tty->flags)) tty->throttle(tty, TTY_THROTTLE_RQ_AVAIL); } int is_ignored(int sig) { return ((current->blocked & (1<<(sig-1))) || (current->sigaction[sig-1].sa_handler == SIG_IGN)); } static inline int input_available_p(struct tty_struct *tty) { /* Avoid calling TTY_READ_FLUSH unnecessarily. */ if (L_ICANON(tty)) { if (tty->canon_data || FULL(&tty->read_q)) return 1; } else if (!EMPTY(&tty->secondary)) return 1; /* Shuffle any pending data down the queues. */ TTY_READ_FLUSH(tty); if (tty->link) TTY_WRITE_FLUSH(tty->link); if (L_ICANON(tty)) { if (tty->canon_data || FULL(&tty->read_q)) return 1; } else if (!EMPTY(&tty->secondary)) return 1; return 0; } static int read_chan(struct tty_struct *tty, struct file *file, unsigned char *buf, unsigned int nr) { struct wait_queue wait = { current, NULL }; int c; unsigned char *b = buf; int minimum, time; int retval = 0; if (L_ICANON(tty)) { minimum = time = 0; current->timeout = (unsigned long) -1; } else { time = (HZ / 10) * TIME_CHAR(tty); minimum = MIN_CHAR(tty); if (minimum) current->timeout = (unsigned long) -1; else { if (time) { current->timeout = time + jiffies; time = 0; } else current->timeout = 0; minimum = 1; } } add_wait_queue(&tty->secondary.proc_list, &wait); while (1) { /* Job control check -- must be done at start and after every sleep (POSIX.1 7.1.1.4). */ /* don't stop on /dev/console */ if (file->f_inode->i_rdev != CONSOLE_DEV && current->tty == tty->line) { if (tty->pgrp <= 0) printk("read_chan: tty->pgrp <= 0!\n"); else if (current->pgrp != tty->pgrp) { if (is_ignored(SIGTTIN) || is_orphaned_pgrp(current->pgrp)) { retval = -EIO; break; } kill_pg(current->pgrp, SIGTTIN, 1); retval = -ERESTARTSYS; break; } } /* First test for status change. */ if (tty->packet && tty->link->ctrl_status) { if (b != buf) break; put_fs_byte(tty->link->ctrl_status, b++); tty->link->ctrl_status = 0; break; } /* This statement must be first before checking for input so that any interrupt will set the state back to TASK_RUNNING. */ current->state = TASK_INTERRUPTIBLE; if (!input_available_p(tty)) { if (tty->flags & (1 << TTY_SLAVE_CLOSED)) { retval = -EIO; break; } if (tty_hung_up_p(file)) break; if (!current->timeout) break; if (file->f_flags & O_NONBLOCK) { retval = -EAGAIN; break; } if (current->signal & ~current->blocked) { retval = -ERESTARTSYS; break; } schedule(); continue; } current->state = TASK_RUNNING; /* Deal with packet mode. */ if (tty->packet && b == buf) { put_fs_byte(TIOCPKT_DATA, b++); nr--; } while (nr > 0) { int eol; cli(); if (EMPTY(&tty->secondary)) { sti(); break; } eol = clear_bit(tty->secondary.tail, &tty->secondary_flags); c = tty->secondary.buf[tty->secondary.tail]; INC(tty->secondary.tail); sti(); if (eol) { if (--tty->canon_data < 0) { printk("read_chan: canon_data < 0!\n"); tty->canon_data = 0; } if (c == __DISABLED_CHAR) break; put_fs_byte(c, b++); nr--; break; } put_fs_byte(c, b++); nr--; } /* If there is enough space in the secondary queue now, let the low-level driver know. */ if (tty->throttle && (LEFT(&tty->secondary) >= SQ_THRESHOLD_HW) && !clear_bit(TTY_SQ_THROTTLED, &tty->flags)) tty->throttle(tty, TTY_THROTTLE_SQ_AVAIL); /* XXX packet mode's status byte is mistakenly counted */ if (b - buf >= minimum || !nr) break; if (time) current->timeout = time + jiffies; } remove_wait_queue(&tty->secondary.proc_list, &wait); current->state = TASK_RUNNING; current->timeout = 0; return (b - buf) ? b - buf : retval; } static int write_chan(struct tty_struct * tty, struct file * file, unsigned char * buf, unsigned int nr) { struct wait_queue wait = { current, NULL }; int c; unsigned char *b = buf; int retval = 0; /* Job control check -- must be done at start (POSIX.1 7.1.1.4). */ if (L_TOSTOP(tty) && file->f_inode->i_rdev != CONSOLE_DEV) { retval = check_change(tty, tty->line); if (retval) return retval; } add_wait_queue(&tty->write_q.proc_list, &wait); while (1) { current->state = TASK_INTERRUPTIBLE; if (current->signal & ~current->blocked) { retval = -ERESTARTSYS; break; } if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) { retval = -EIO; break; } while (nr > 0) { c = get_fs_byte(b); /* Care is needed here: opost() can abort even if the write_q is not full. */ if (opost(c, tty) < 0) break; b++; nr--; } TTY_WRITE_FLUSH(tty); if (!nr) break; if (EMPTY(&tty->write_q) && !need_resched) continue; schedule(); } current->state = TASK_RUNNING; remove_wait_queue(&tty->write_q.proc_list, &wait); return (b - buf) ? b - buf : retval; } static int tty_read(struct inode * inode, struct file * file, char * buf, int count) { int i, dev; struct tty_struct * tty; dev = file->f_rdev; if (MAJOR(dev) != TTY_MAJOR) { printk("tty_read: bad pseudo-major nr #%d\n", MAJOR(dev)); return -EINVAL; } dev = MINOR(dev); tty = TTY_TABLE(dev); if (!tty || (tty->flags & (1 << TTY_IO_ERROR))) return -EIO; /* This check not only needs to be done before reading, but also whenever read_chan() gets woken up after sleeping, so I've moved it to there. This should only be done for the N_TTY line discipline, anyway. Same goes for write_chan(). -- jlc. */ #if 0 if ((inode->i_rdev != CONSOLE_DEV) && /* don't stop on /dev/console */ (tty->pgrp > 0) && (current->tty == dev) && (tty->pgrp != current->pgrp)) if (is_ignored(SIGTTIN) || is_orphaned_pgrp(current->pgrp)) return -EIO; else { (void) kill_pg(current->pgrp, SIGTTIN, 1); return -ERESTARTSYS; } #endif if (ldiscs[tty->disc].read) /* XXX casts are for what kernel-wide prototypes should be. */ i = (ldiscs[tty->disc].read)(tty,file,(unsigned char *)buf,(unsigned int)count); else i = -EIO; if (i > 0) inode->i_atime = CURRENT_TIME; return i; } static int tty_write(struct inode * inode, struct file * file, char * buf, int count) { int dev, i, is_console; struct tty_struct * tty; dev = file->f_rdev; is_console = (inode->i_rdev == CONSOLE_DEV); if (MAJOR(dev) != TTY_MAJOR) { printk("tty_write: pseudo-major != TTY_MAJOR\n"); return -EINVAL; } dev = MINOR(dev); if (is_console && redirect) tty = redirect; else tty = TTY_TABLE(dev); if (!tty || !tty->write || (tty->flags & (1 << TTY_IO_ERROR))) return -EIO; #if 0 if (!is_console && L_TOSTOP(tty) && (tty->pgrp > 0) && (current->tty == dev) && (tty->pgrp != current->pgrp)) { if (is_orphaned_pgrp(current->pgrp)) return -EIO; if (!is_ignored(SIGTTOU)) { (void) kill_pg(current->pgrp, SIGTTOU, 1); return -ERESTARTSYS; } } #endif if (ldiscs[tty->disc].write) /* XXX casts are for what kernel-wide prototypes should be. */ i = (ldiscs[tty->disc].write)(tty,file,(unsigned char *)buf,(unsigned int)count); else i = -EIO; if (i > 0) inode->i_mtime = CURRENT_TIME; return i; } /* * This is so ripe with races that you should *really* not touch this * unless you know exactly what you are doing. All the changes have to be * made atomically, or there may be incorrect pointers all over the place. */ static int init_dev(int dev) { struct tty_struct *tty, *o_tty; struct termios *tp, *o_tp, *ltp, *o_ltp; int retval; int o_dev; o_dev = PTY_OTHER(dev); tty = o_tty = NULL; tp = o_tp = NULL; ltp = o_ltp = NULL; repeat: retval = -EAGAIN; if (IS_A_PTY_MASTER(dev) && tty_table[dev] && tty_table[dev]->count) goto end_init; retval = -ENOMEM; if (!tty_table[dev] && !tty) { if (!(tty = (struct tty_struct*) get_free_page(GFP_KERNEL))) goto end_init; initialize_tty_struct(dev, tty); goto repeat; } if (!tty_termios[dev] && !tp) { tp = (struct termios *) kmalloc(sizeof(struct termios), GFP_KERNEL); if (!tp) goto end_init; initialize_termios(dev, tp); goto repeat; } if (!termios_locked[dev] && !ltp) { ltp = (struct termios *) kmalloc(sizeof(struct termios), GFP_KERNEL); if (!ltp) goto end_init; memset(ltp, 0, sizeof(struct termios)); goto repeat; } if (IS_A_PTY(dev)) { if (!tty_table[o_dev] && !o_tty) { o_tty = (struct tty_struct *) get_free_page(GFP_KERNEL); if (!o_tty) goto end_init; initialize_tty_struct(o_dev, o_tty); goto repeat; } if (!tty_termios[o_dev] && !o_tp) { o_tp = (struct termios *) kmalloc(sizeof(struct termios), GFP_KERNEL); if (!o_tp) goto end_init; initialize_termios(o_dev, o_tp); goto repeat; } if (!termios_locked[o_dev] && !o_ltp) { o_ltp = (struct termios *) kmalloc(sizeof(struct termios), GFP_KERNEL); if (!o_ltp) goto end_init; memset(o_ltp, 0, sizeof(struct termios)); goto repeat; } } /* Now we have allocated all the structures: update all the pointers.. */ if (!tty_termios[dev]) { tty_termios[dev] = tp; tp = NULL; } if (!tty_table[dev]) { tty->termios = tty_termios[dev]; tty_table[dev] = tty; tty = NULL; } if (!termios_locked[dev]) { termios_locked[dev] = ltp; ltp = NULL; } if (IS_A_PTY(dev)) { if (!tty_termios[o_dev]) { tty_termios[o_dev] = o_tp; o_tp = NULL; } if (!termios_locked[o_dev]) { termios_locked[o_dev] = o_ltp; o_ltp = NULL; } if (!tty_table[o_dev]) { o_tty->termios = tty_termios[o_dev]; tty_table[o_dev] = o_tty; o_tty = NULL; } tty_table[dev]->link = tty_table[o_dev]; tty_table[o_dev]->link = tty_table[dev]; } tty_table[dev]->count++; if (IS_A_PTY_MASTER(dev)) tty_table[o_dev]->count++; retval = 0; end_init: if (tty) free_page((unsigned long) tty); if (o_tty) free_page((unsigned long) o_tty); if (tp) kfree_s(tp, sizeof(struct termios)); if (o_tp) kfree_s(o_tp, sizeof(struct termios)); if (ltp) kfree_s(ltp, sizeof(struct termios)); if (o_ltp) kfree_s(o_ltp, sizeof(struct termios)); return retval; } /* * Even releasing the tty structures is a tricky business.. We have * to be very careful that the structures are all released at the * same time, as interrupts might otherwise get the wrong pointers. */ static void release_dev(int dev, struct file * filp) { struct tty_struct *tty, *o_tty; struct termios *tp, *o_tp; struct task_struct **p; tty = tty_table[dev]; tp = tty_termios[dev]; o_tty = NULL; o_tp = NULL; if (!tty) { printk("release_dev: tty_table[%d] was NULL\n", dev); return; } if (!tp) { printk("release_dev: tty_termios[%d] was NULL\n", dev); return; } #ifdef TTY_DEBUG_HANGUP printk("release_dev of tty%d (tty count=%d)...", dev, tty->count); #endif if (IS_A_PTY(dev)) { o_tty = tty_table[PTY_OTHER(dev)]; o_tp = tty_termios[PTY_OTHER(dev)]; if (!o_tty) { printk("release_dev: pty pair(%d) was NULL\n", dev); return; } if (!o_tp) { printk("release_dev: pty pair(%d) termios was NULL\n", dev); return; } if (tty->link != o_tty || o_tty->link != tty) { printk("release_dev: bad pty pointers\n"); return; } } tty->write_data_cnt = 0; /* Clear out pending trash */ if (tty->close) tty->close(tty, filp); if (IS_A_PTY_MASTER(dev)) { if (--tty->link->count < 0) { printk("release_dev: bad tty slave count (dev = %d): %d\n", dev, tty->count); tty->link->count = 0; } } if (--tty->count < 0) { printk("release_dev: bad tty_table[%d]->count: %d\n", dev, tty->count); tty->count = 0; } if (tty->count) return; #ifdef TTY_DEBUG_HANGUP printk("freeing tty structure..."); #endif /* * Make sure there aren't any processes that still think this * tty is their controlling tty. */ for (p = &LAST_TASK ; p > &FIRST_TASK ; --p) { if ((*p) && (*p)->tty == tty->line) (*p)->tty = -1; } /* * Shutdown the current line discipline, and reset it to * N_TTY. */ if (ldiscs[tty->disc].close != NULL) ldiscs[tty->disc].close(tty); tty->disc = N_TTY; tty->termios->c_line = N_TTY; if (o_tty) { if (o_tty->count) return; else { tty_table[PTY_OTHER(dev)] = NULL; tty_termios[PTY_OTHER(dev)] = NULL; } } tty_table[dev] = NULL; if (IS_A_PTY(dev)) { tty_termios[dev] = NULL; kfree_s(tp, sizeof(struct termios)); } if (tty == redirect || o_tty == redirect) redirect = NULL; free_page((unsigned long) tty); if (o_tty) free_page((unsigned long) o_tty); if (o_tp) kfree_s(o_tp, sizeof(struct termios)); } /* * tty_open and tty_release keep up the tty count that contains the * number of opens done on a tty. We cannot use the inode-count, as * different inodes might point to the same tty. * * Open-counting is needed for pty masters, as well as for keeping * track of serial lines: DTR is dropped when the last close happens. * (This is not done solely through tty->count, now. - Ted 1/27/92) * * The termios state of a pty is reset on first open so that * settings don't persist across reuse. */ static int tty_open(struct inode * inode, struct file * filp) { struct tty_struct *tty; int major, minor; int noctty, retval; retry_open: minor = MINOR(inode->i_rdev); major = MAJOR(inode->i_rdev); noctty = filp->f_flags & O_NOCTTY; if (major == TTYAUX_MAJOR) { if (!minor) { major = TTY_MAJOR; minor = current->tty; } /* noctty = 1; */ } else if (major == TTY_MAJOR) { if (!minor) { minor = fg_console + 1; noctty = 1; } } else { printk("Bad major #%d in tty_open\n", MAJOR(inode->i_rdev)); return -ENODEV; } if (minor <= 0) return -ENXIO; if (IS_A_PTY_MASTER(minor)) noctty = 1; filp->f_rdev = (major << 8) | minor; retval = init_dev(minor); if (retval) return retval; tty = tty_table[minor]; #ifdef TTY_DEBUG_HANGUP printk("opening tty%d...", tty->line); #endif if (test_bit(TTY_EXCLUSIVE, &tty->flags) && !suser()) return -EBUSY; #if 0 /* clean up the packet stuff. */ /* * Why is this not done in init_dev? Right here, if another * process opens up a tty in packet mode, all the packet * variables get cleared. Come to think of it, is anything * using the packet mode at all??? - Ted, 1/27/93 * * Not to worry, a pty master can only be opened once. * And rlogind and telnetd both use packet mode. -- jrs * * Not needed. These are cleared in initialize_tty_struct. -- jlc */ tty->ctrl_status = 0; tty->packet = 0; #endif if (tty->open) { retval = tty->open(tty, filp); } else { retval = -ENODEV; } if (retval) { #ifdef TTY_DEBUG_HANGUP printk("error %d in opening tty%d...", retval, tty->line); #endif release_dev(minor, filp); if (retval != -ERESTARTSYS) return retval; if (current->signal & ~current->blocked) return retval; schedule(); goto retry_open; } if (!noctty && current->leader && current->tty<0 && tty->session==0) { current->tty = minor; tty->session = current->session; tty->pgrp = current->pgrp; } filp->f_rdev = MKDEV(TTY_MAJOR,minor); /* Set it to something normal */ return 0; } /* * Note that releasing a pty master also releases the child, so * we have to make the redirection checks after that and on both * sides of a pty. */ static void tty_release(struct inode * inode, struct file * filp) { int dev; dev = filp->f_rdev; if (MAJOR(dev) != TTY_MAJOR) { printk("tty_release: tty pseudo-major != TTY_MAJOR\n"); return; } dev = MINOR(filp->f_rdev); if (!dev) { printk("tty_release: bad f_rdev\n"); return; } release_dev(dev, filp); } static int tty_select(struct inode * inode, struct file * filp, int sel_type, select_table * wait) { int dev; struct tty_struct * tty; dev = filp->f_rdev; if (MAJOR(dev) != TTY_MAJOR) { printk("tty_select: tty pseudo-major != TTY_MAJOR\n"); return 0; } dev = MINOR(filp->f_rdev); tty = TTY_TABLE(dev); if (!tty) { printk("tty_select: tty struct for dev %d was NULL\n", dev); return 0; } if (ldiscs[tty->disc].select) return (ldiscs[tty->disc].select)(tty, inode, filp, sel_type, wait); return 0; } static int normal_select(struct tty_struct * tty, struct inode * inode, struct file * file, int sel_type, select_table *wait) { switch (sel_type) { case SEL_IN: if (input_available_p(tty)) return 1; /* fall through */ case SEL_EX: if (tty->packet && tty->link->ctrl_status) return 1; if (tty->flags & (1 << TTY_SLAVE_CLOSED)) return 1; if (tty_hung_up_p(file)) return 1; select_wait(&tty->secondary.proc_list, wait); return 0; case SEL_OUT: if (!FULL(&tty->write_q)) return 1; select_wait(&tty->write_q.proc_list, wait); return 0; } return 0; } /* * This implements the "Secure Attention Key" --- the idea is to * prevent trojan horses by killing all processes associated with this * tty when the user hits the "Secure Attention Key". Required for * super-paranoid applications --- see the Orange Book for more details. * * This code could be nicer; ideally it should send a HUP, wait a few * seconds, then send a INT, and then a KILL signal. But you then * have to coordinate with the init process, since all processes associated * with the current tty must be dead before the new getty is allowed * to spawn. */ void do_SAK( struct tty_struct *tty) { #ifdef TTY_SOFT_SAK tty_hangup(tty); #else struct task_struct **p; int line = tty->line; int session = tty->session; int i; struct file *filp; flush_input(tty); flush_output(tty); for (p = &LAST_TASK ; p > &FIRST_TASK ; --p) { if (!(*p)) continue; if (((*p)->tty == line) || ((session > 0) && ((*p)->session == session))) send_sig(SIGKILL, *p, 1); else { for (i=0; i < NR_OPEN; i++) { filp = (*p)->filp[i]; if (filp && (filp->f_op == &tty_fops) && (MINOR(filp->f_rdev) == line)) { send_sig(SIGKILL, *p, 1); break; } } } } #endif } /* * This routine allows a kernel routine to send a large chunk of data * to a particular tty; if all of the data can be queued up for ouput * immediately, tty_write_data() will return 0. If, however, not all * of the data can be immediately queued for delivery, the number of * bytes left to be queued up will be returned, and the rest of the * data will be queued up when there is room. The callback function * will be called (with the argument callarg) when the last of the * data is finally in the queue. * * Note that the callback routine will _not_ be called if all of the * data could be queued immediately. This is to avoid a problem with * the kernel stack getting too deep, which might happen if the * callback routine calls tty_write_data with itself as an argument. */ int tty_write_data(struct tty_struct *tty, char *bufp, int buflen, void (*callback)(void * data), void * callarg) { int head, tail, count; unsigned long flags; char *p; #define VLEFT ((tail-head-1)&(TTY_BUF_SIZE-1)) save_flags(flags); cli(); if (tty->write_data_cnt) { restore_flags(flags); return -EBUSY; } head = tty->write_q.head; tail = tty->write_q.tail; count = buflen; p = bufp; while (count && VLEFT > 0) { tty->write_q.buf[head++] = *p++; head &= TTY_BUF_SIZE-1; count--; } tty->write_q.head = head; if (count) { tty->write_data_cnt = count; tty->write_data_ptr = (unsigned char *) p; tty->write_data_callback = callback; tty->write_data_arg = callarg; } restore_flags(flags); tty->write(tty); return count; } /* * This routine routine is called after an interrupt has drained a * tty's write queue, so that there is more space for data waiting to * be sent in tty->write_data_ptr. * * tty_check_write[8] is a bitstring which indicates which ttys * needs to be processed. */ void tty_bh_routine(void * unused) { int i, j, line, mask; int head, tail, count; unsigned char * p; struct tty_struct * tty; for (i = 0, line = 0; i < MAX_TTYS / 32; i++) { if (!tty_check_write[i]) { line += 32; continue; } for (j=0, mask=0; j < 32; j++, line++, mask <<= 1) { if (clear_bit(j, &tty_check_write[i])) { tty = tty_table[line]; if (!tty || !tty->write_data_cnt) continue; cli(); head = tty->write_q.head; tail = tty->write_q.tail; count = tty->write_data_cnt; p = tty->write_data_ptr; while (count && VLEFT > 0) { tty->write_q.buf[head++] = *p++; head &= TTY_BUF_SIZE-1; count--; } tty->write_q.head = head; tty->write_data_ptr = p; tty->write_data_cnt = count; sti(); if (!count) (tty->write_data_callback) (tty->write_data_arg); } } } } /* * This subroutine initializes a tty structure. We have to set up * things correctly for each different type of tty. */ static void initialize_tty_struct(int line, struct tty_struct *tty) { memset(tty, 0, sizeof(struct tty_struct)); tty->line = line; tty->disc = N_TTY; tty->pgrp = -1; if (IS_A_CONSOLE(line)) { tty->open = con_open; tty->winsize.ws_row = video_num_lines; tty->winsize.ws_col = video_num_columns; } else if IS_A_SERIAL(line) { tty->open = rs_open; } else if IS_A_PTY(line) { tty->open = pty_open; } } static void initialize_termios(int line, struct termios * tp) { memset(tp, 0, sizeof(struct termios)); memcpy(tp->c_cc, INIT_C_CC, NCCS); if (IS_A_CONSOLE(line) || IS_A_PTY_SLAVE(line)) { tp->c_iflag = ICRNL | IXON; tp->c_oflag = OPOST | ONLCR; tp->c_cflag = B38400 | CS8 | CREAD; tp->c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN; } else if (IS_A_SERIAL(line)) { tp->c_iflag = ICRNL | IXON; tp->c_oflag = OPOST | ONLCR | XTABS; tp->c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; tp->c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN; } else if (IS_A_PTY_MASTER(line)) tp->c_cflag = B9600 | CS8 | CREAD; } static struct tty_ldisc tty_ldisc_N_TTY = { 0, /* flags */ NULL, /* open */ NULL, /* close */ read_chan, /* read */ write_chan, /* write */ NULL, /* ioctl */ normal_select, /* select */ copy_to_cooked /* handler */ }; long tty_init(long kmem_start) { int i; if (sizeof(struct tty_struct) > PAGE_SIZE) panic("size of tty structure > PAGE_SIZE!"); if (register_chrdev(TTY_MAJOR,"tty",&tty_fops)) panic("unable to get major %d for tty device", TTY_MAJOR); if (register_chrdev(TTYAUX_MAJOR,"tty",&tty_fops)) panic("unable to get major %d for tty device", TTYAUX_MAJOR); for (i=0 ; i< MAX_TTYS ; i++) { tty_table[i] = 0; tty_termios[i] = 0; } memset(tty_check_write, 0, sizeof(tty_check_write)); bh_base[TTY_BH].routine = tty_bh_routine; /* Setup the default TTY line discipline. */ memset(ldiscs, 0, sizeof(ldiscs)); (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY); kmem_start = kbd_init(kmem_start); kmem_start = con_init(kmem_start); kmem_start = rs_init(kmem_start); return kmem_start; }