831d27a9f5
by bde, a few other tweaks to get the patch to apply cleanly again and some improvements to the comments. This change closes some fairly minor security holes associated with F_SETOWN, fixes a few bugs, and removes some limitations that F_SETOWN had on tty devices. For more details, see the description on the PR. Because this patch increases the size of the proc and pgrp structures, it is necessary to re-install the includes and recompile libkvm, the vinum lkm, fstat, gcore, gdb, ipfilter, ps, top, and w. PR: kern/7899 Reviewed by: bde, elvind
2423 lines
57 KiB
C
2423 lines
57 KiB
C
/*-
|
|
* Copyright (c) 1982, 1986, 1990, 1991, 1993
|
|
* The Regents of the University of California. All rights reserved.
|
|
* (c) UNIX System Laboratories, Inc.
|
|
* All or some portions of this file are derived from material licensed
|
|
* to the University of California by American Telephone and Telegraph
|
|
* Co. or Unix System Laboratories, Inc. and are reproduced herein with
|
|
* the permission of UNIX System Laboratories, Inc.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by the University of
|
|
* California, Berkeley and its contributors.
|
|
* 4. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
* @(#)tty.c 8.8 (Berkeley) 1/21/94
|
|
* $Id: tty.c,v 1.106 1998/08/19 04:01:00 bde Exp $
|
|
*/
|
|
|
|
/*-
|
|
* TODO:
|
|
* o Fix races for sending the start char in ttyflush().
|
|
* o Handle inter-byte timeout for "MIN > 0, TIME > 0" in ttyselect().
|
|
* With luck, there will be MIN chars before select() returns().
|
|
* o Handle CLOCAL consistently for ptys. Perhaps disallow setting it.
|
|
* o Don't allow input in TS_ZOMBIE case. It would be visible through
|
|
* FIONREAD.
|
|
* o Do the new sio locking stuff here and use it to avoid special
|
|
* case for EXTPROC?
|
|
* o Lock PENDIN too?
|
|
* o Move EXTPROC and/or PENDIN to t_state?
|
|
* o Wrap most of ttioctl in spltty/splx.
|
|
* o Implement TIOCNOTTY or remove it from <sys/ioctl.h>.
|
|
* o Send STOP if IXOFF is toggled off while TS_TBLOCK is set.
|
|
* o Don't allow certain termios flags to affect disciplines other
|
|
* than TTYDISC. Cancel their effects before switch disciplines
|
|
* and ignore them if they are set while we are in another
|
|
* discipline.
|
|
* o Handle c_ispeed = 0 to c_ispeed = c_ospeed conversion here instead
|
|
* of in drivers and fix drivers that write to tp->t_termios.
|
|
* o Check for TS_CARR_ON being set while everything is closed and not
|
|
* waiting for carrier. TS_CARR_ON isn't cleared if nothing is open,
|
|
* so it would live until the next open even if carrier drops.
|
|
* o Restore TS_WOPEN since it is useful in pstat. It must be cleared
|
|
* only when _all_ openers leave open().
|
|
*/
|
|
|
|
#include "snp.h"
|
|
#include "opt_compat.h"
|
|
#include "opt_uconsole.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/filio.h>
|
|
#if defined(COMPAT_43) || defined(COMPAT_SUNOS)
|
|
#include <sys/ioctl_compat.h>
|
|
#endif
|
|
#include <sys/proc.h>
|
|
#define TTYDEFCHARS
|
|
#include <sys/tty.h>
|
|
#undef TTYDEFCHARS
|
|
#include <sys/fcntl.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/dkstat.h>
|
|
#include <sys/poll.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/vnode.h>
|
|
#include <sys/signalvar.h>
|
|
#include <sys/resourcevar.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/filedesc.h>
|
|
#if NSNP > 0
|
|
#include <sys/snoop.h>
|
|
#endif
|
|
|
|
#include <vm/vm.h>
|
|
#include <sys/lock.h>
|
|
#include <vm/pmap.h>
|
|
#include <vm/vm_map.h>
|
|
|
|
MALLOC_DEFINE(M_TTYS, "ttys", "tty data structures");
|
|
|
|
static int proc_compare __P((struct proc *p1, struct proc *p2));
|
|
static int ttnread __P((struct tty *tp));
|
|
static void ttyecho __P((int c, struct tty *tp));
|
|
static int ttyoutput __P((int c, register struct tty *tp));
|
|
static void ttypend __P((struct tty *tp));
|
|
static void ttyretype __P((struct tty *tp));
|
|
static void ttyrub __P((int c, struct tty *tp));
|
|
static void ttyrubo __P((struct tty *tp, int cnt));
|
|
static void ttyunblock __P((struct tty *tp));
|
|
static int ttywflush __P((struct tty *tp));
|
|
|
|
/*
|
|
* Table with character classes and parity. The 8th bit indicates parity,
|
|
* the 7th bit indicates the character is an alphameric or underscore (for
|
|
* ALTWERASE), and the low 6 bits indicate delay type. If the low 6 bits
|
|
* are 0 then the character needs no special processing on output; classes
|
|
* other than 0 might be translated or (not currently) require delays.
|
|
*/
|
|
#define E 0x00 /* Even parity. */
|
|
#define O 0x80 /* Odd parity. */
|
|
#define PARITY(c) (char_type[c] & O)
|
|
|
|
#define ALPHA 0x40 /* Alpha or underscore. */
|
|
#define ISALPHA(c) (char_type[(c) & TTY_CHARMASK] & ALPHA)
|
|
|
|
#define CCLASSMASK 0x3f
|
|
#define CCLASS(c) (char_type[c] & CCLASSMASK)
|
|
|
|
#define BS BACKSPACE
|
|
#define CC CONTROL
|
|
#define CR RETURN
|
|
#define NA ORDINARY | ALPHA
|
|
#define NL NEWLINE
|
|
#define NO ORDINARY
|
|
#define TB TAB
|
|
#define VT VTAB
|
|
|
|
static u_char const char_type[] = {
|
|
E|CC, O|CC, O|CC, E|CC, O|CC, E|CC, E|CC, O|CC, /* nul - bel */
|
|
O|BS, E|TB, E|NL, O|CC, E|VT, O|CR, O|CC, E|CC, /* bs - si */
|
|
O|CC, E|CC, E|CC, O|CC, E|CC, O|CC, O|CC, E|CC, /* dle - etb */
|
|
E|CC, O|CC, O|CC, E|CC, O|CC, E|CC, E|CC, O|CC, /* can - us */
|
|
O|NO, E|NO, E|NO, O|NO, E|NO, O|NO, O|NO, E|NO, /* sp - ' */
|
|
E|NO, O|NO, O|NO, E|NO, O|NO, E|NO, E|NO, O|NO, /* ( - / */
|
|
E|NA, O|NA, O|NA, E|NA, O|NA, E|NA, E|NA, O|NA, /* 0 - 7 */
|
|
O|NA, E|NA, E|NO, O|NO, E|NO, O|NO, O|NO, E|NO, /* 8 - ? */
|
|
O|NO, E|NA, E|NA, O|NA, E|NA, O|NA, O|NA, E|NA, /* @ - G */
|
|
E|NA, O|NA, O|NA, E|NA, O|NA, E|NA, E|NA, O|NA, /* H - O */
|
|
E|NA, O|NA, O|NA, E|NA, O|NA, E|NA, E|NA, O|NA, /* P - W */
|
|
O|NA, E|NA, E|NA, O|NO, E|NO, O|NO, O|NO, O|NA, /* X - _ */
|
|
E|NO, O|NA, O|NA, E|NA, O|NA, E|NA, E|NA, O|NA, /* ` - g */
|
|
O|NA, E|NA, E|NA, O|NA, E|NA, O|NA, O|NA, E|NA, /* h - o */
|
|
O|NA, E|NA, E|NA, O|NA, E|NA, O|NA, O|NA, E|NA, /* p - w */
|
|
E|NA, O|NA, O|NA, E|NO, O|NO, E|NO, E|NO, O|CC, /* x - del */
|
|
/*
|
|
* Meta chars; should be settable per character set;
|
|
* for now, treat them all as normal characters.
|
|
*/
|
|
NA, NA, NA, NA, NA, NA, NA, NA,
|
|
NA, NA, NA, NA, NA, NA, NA, NA,
|
|
NA, NA, NA, NA, NA, NA, NA, NA,
|
|
NA, NA, NA, NA, NA, NA, NA, NA,
|
|
NA, NA, NA, NA, NA, NA, NA, NA,
|
|
NA, NA, NA, NA, NA, NA, NA, NA,
|
|
NA, NA, NA, NA, NA, NA, NA, NA,
|
|
NA, NA, NA, NA, NA, NA, NA, NA,
|
|
NA, NA, NA, NA, NA, NA, NA, NA,
|
|
NA, NA, NA, NA, NA, NA, NA, NA,
|
|
NA, NA, NA, NA, NA, NA, NA, NA,
|
|
NA, NA, NA, NA, NA, NA, NA, NA,
|
|
NA, NA, NA, NA, NA, NA, NA, NA,
|
|
NA, NA, NA, NA, NA, NA, NA, NA,
|
|
NA, NA, NA, NA, NA, NA, NA, NA,
|
|
NA, NA, NA, NA, NA, NA, NA, NA,
|
|
};
|
|
#undef BS
|
|
#undef CC
|
|
#undef CR
|
|
#undef NA
|
|
#undef NL
|
|
#undef NO
|
|
#undef TB
|
|
#undef VT
|
|
|
|
/* Macros to clear/set/test flags. */
|
|
#define SET(t, f) (t) |= (f)
|
|
#define CLR(t, f) (t) &= ~(f)
|
|
#define ISSET(t, f) ((t) & (f))
|
|
|
|
#undef MAX_INPUT /* XXX wrong in <sys/syslimits.h> */
|
|
#define MAX_INPUT TTYHOG /* XXX limit is usually larger for !ICANON */
|
|
|
|
/*
|
|
* Initial open of tty, or (re)entry to standard tty line discipline.
|
|
*/
|
|
int
|
|
ttyopen(device, tp)
|
|
dev_t device;
|
|
register struct tty *tp;
|
|
{
|
|
int s;
|
|
|
|
s = spltty();
|
|
tp->t_dev = device;
|
|
if (!ISSET(tp->t_state, TS_ISOPEN)) {
|
|
SET(tp->t_state, TS_ISOPEN);
|
|
if (ISSET(tp->t_cflag, CLOCAL))
|
|
SET(tp->t_state, TS_CONNECTED);
|
|
bzero(&tp->t_winsize, sizeof(tp->t_winsize));
|
|
}
|
|
ttsetwater(tp);
|
|
splx(s);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Handle close() on a tty line: flush and set to initial state,
|
|
* bumping generation number so that pending read/write calls
|
|
* can detect recycling of the tty.
|
|
* XXX our caller should have done `spltty(); l_close(); ttyclose();'
|
|
* and l_close() should have flushed, but we repeat the spltty() and
|
|
* the flush in case there are buggy callers.
|
|
*/
|
|
int
|
|
ttyclose(tp)
|
|
register struct tty *tp;
|
|
{
|
|
int s;
|
|
|
|
funsetown(tp->t_sigio);
|
|
s = spltty();
|
|
if (constty == tp)
|
|
constty = NULL;
|
|
|
|
ttyflush(tp, FREAD | FWRITE);
|
|
clist_free_cblocks(&tp->t_canq);
|
|
clist_free_cblocks(&tp->t_outq);
|
|
clist_free_cblocks(&tp->t_rawq);
|
|
|
|
#if NSNP > 0
|
|
if (ISSET(tp->t_state, TS_SNOOP) && tp->t_sc != NULL)
|
|
snpdown((struct snoop *)tp->t_sc);
|
|
#endif
|
|
|
|
tp->t_gen++;
|
|
tp->t_line = TTYDISC;
|
|
tp->t_pgrp = NULL;
|
|
tp->t_session = NULL;
|
|
tp->t_state = 0;
|
|
splx(s);
|
|
return (0);
|
|
}
|
|
|
|
#define FLUSHQ(q) { \
|
|
if ((q)->c_cc) \
|
|
ndflush(q, (q)->c_cc); \
|
|
}
|
|
|
|
/* Is 'c' a line delimiter ("break" character)? */
|
|
#define TTBREAKC(c, lflag) \
|
|
((c) == '\n' || (((c) == cc[VEOF] || \
|
|
(c) == cc[VEOL] || ((c) == cc[VEOL2] && lflag & IEXTEN)) && \
|
|
(c) != _POSIX_VDISABLE))
|
|
|
|
/*
|
|
* Process input of a single character received on a tty.
|
|
*/
|
|
int
|
|
ttyinput(c, tp)
|
|
register int c;
|
|
register struct tty *tp;
|
|
{
|
|
register tcflag_t iflag, lflag;
|
|
register cc_t *cc;
|
|
int i, err;
|
|
|
|
/*
|
|
* If input is pending take it first.
|
|
*/
|
|
lflag = tp->t_lflag;
|
|
if (ISSET(lflag, PENDIN))
|
|
ttypend(tp);
|
|
/*
|
|
* Gather stats.
|
|
*/
|
|
if (ISSET(lflag, ICANON)) {
|
|
++tk_cancc;
|
|
++tp->t_cancc;
|
|
} else {
|
|
++tk_rawcc;
|
|
++tp->t_rawcc;
|
|
}
|
|
++tk_nin;
|
|
|
|
/*
|
|
* Block further input iff:
|
|
* current input > threshold AND input is available to user program
|
|
* AND input flow control is enabled and not yet invoked.
|
|
* The 3 is slop for PARMRK.
|
|
*/
|
|
iflag = tp->t_iflag;
|
|
if (tp->t_rawq.c_cc + tp->t_canq.c_cc > tp->t_ihiwat - 3 &&
|
|
(!ISSET(lflag, ICANON) || tp->t_canq.c_cc != 0) &&
|
|
(ISSET(tp->t_cflag, CRTS_IFLOW) || ISSET(iflag, IXOFF)) &&
|
|
!ISSET(tp->t_state, TS_TBLOCK))
|
|
ttyblock(tp);
|
|
|
|
/* Handle exceptional conditions (break, parity, framing). */
|
|
cc = tp->t_cc;
|
|
err = (ISSET(c, TTY_ERRORMASK));
|
|
if (err) {
|
|
CLR(c, TTY_ERRORMASK);
|
|
if (ISSET(err, TTY_BI)) {
|
|
if (ISSET(iflag, IGNBRK))
|
|
return (0);
|
|
if (ISSET(iflag, BRKINT)) {
|
|
ttyflush(tp, FREAD | FWRITE);
|
|
pgsignal(tp->t_pgrp, SIGINT, 1);
|
|
goto endcase;
|
|
}
|
|
if (ISSET(iflag, PARMRK))
|
|
goto parmrk;
|
|
} else if ((ISSET(err, TTY_PE) && ISSET(iflag, INPCK))
|
|
|| ISSET(err, TTY_FE)) {
|
|
if (ISSET(iflag, IGNPAR))
|
|
return (0);
|
|
else if (ISSET(iflag, PARMRK)) {
|
|
parmrk:
|
|
if (tp->t_rawq.c_cc + tp->t_canq.c_cc >
|
|
MAX_INPUT - 3)
|
|
goto input_overflow;
|
|
(void)putc(0377 | TTY_QUOTE, &tp->t_rawq);
|
|
(void)putc(0 | TTY_QUOTE, &tp->t_rawq);
|
|
(void)putc(c | TTY_QUOTE, &tp->t_rawq);
|
|
goto endcase;
|
|
} else
|
|
c = 0;
|
|
}
|
|
}
|
|
|
|
if (!ISSET(tp->t_state, TS_TYPEN) && ISSET(iflag, ISTRIP))
|
|
CLR(c, 0x80);
|
|
if (!ISSET(lflag, EXTPROC)) {
|
|
/*
|
|
* Check for literal nexting very first
|
|
*/
|
|
if (ISSET(tp->t_state, TS_LNCH)) {
|
|
SET(c, TTY_QUOTE);
|
|
CLR(tp->t_state, TS_LNCH);
|
|
}
|
|
/*
|
|
* Scan for special characters. This code
|
|
* is really just a big case statement with
|
|
* non-constant cases. The bottom of the
|
|
* case statement is labeled ``endcase'', so goto
|
|
* it after a case match, or similar.
|
|
*/
|
|
|
|
/*
|
|
* Control chars which aren't controlled
|
|
* by ICANON, ISIG, or IXON.
|
|
*/
|
|
if (ISSET(lflag, IEXTEN)) {
|
|
if (CCEQ(cc[VLNEXT], c)) {
|
|
if (ISSET(lflag, ECHO)) {
|
|
if (ISSET(lflag, ECHOE)) {
|
|
(void)ttyoutput('^', tp);
|
|
(void)ttyoutput('\b', tp);
|
|
} else
|
|
ttyecho(c, tp);
|
|
}
|
|
SET(tp->t_state, TS_LNCH);
|
|
goto endcase;
|
|
}
|
|
if (CCEQ(cc[VDISCARD], c)) {
|
|
if (ISSET(lflag, FLUSHO))
|
|
CLR(tp->t_lflag, FLUSHO);
|
|
else {
|
|
ttyflush(tp, FWRITE);
|
|
ttyecho(c, tp);
|
|
if (tp->t_rawq.c_cc + tp->t_canq.c_cc)
|
|
ttyretype(tp);
|
|
SET(tp->t_lflag, FLUSHO);
|
|
}
|
|
goto startoutput;
|
|
}
|
|
}
|
|
/*
|
|
* Signals.
|
|
*/
|
|
if (ISSET(lflag, ISIG)) {
|
|
if (CCEQ(cc[VINTR], c) || CCEQ(cc[VQUIT], c)) {
|
|
if (!ISSET(lflag, NOFLSH))
|
|
ttyflush(tp, FREAD | FWRITE);
|
|
ttyecho(c, tp);
|
|
pgsignal(tp->t_pgrp,
|
|
CCEQ(cc[VINTR], c) ? SIGINT : SIGQUIT, 1);
|
|
goto endcase;
|
|
}
|
|
if (CCEQ(cc[VSUSP], c)) {
|
|
if (!ISSET(lflag, NOFLSH))
|
|
ttyflush(tp, FREAD);
|
|
ttyecho(c, tp);
|
|
pgsignal(tp->t_pgrp, SIGTSTP, 1);
|
|
goto endcase;
|
|
}
|
|
}
|
|
/*
|
|
* Handle start/stop characters.
|
|
*/
|
|
if (ISSET(iflag, IXON)) {
|
|
if (CCEQ(cc[VSTOP], c)) {
|
|
if (!ISSET(tp->t_state, TS_TTSTOP)) {
|
|
SET(tp->t_state, TS_TTSTOP);
|
|
#ifdef sun4c /* XXX */
|
|
(*tp->t_stop)(tp, 0);
|
|
#else
|
|
(*cdevsw[major(tp->t_dev)]->d_stop)(tp,
|
|
0);
|
|
#endif
|
|
return (0);
|
|
}
|
|
if (!CCEQ(cc[VSTART], c))
|
|
return (0);
|
|
/*
|
|
* if VSTART == VSTOP then toggle
|
|
*/
|
|
goto endcase;
|
|
}
|
|
if (CCEQ(cc[VSTART], c))
|
|
goto restartoutput;
|
|
}
|
|
/*
|
|
* IGNCR, ICRNL, & INLCR
|
|
*/
|
|
if (c == '\r') {
|
|
if (ISSET(iflag, IGNCR))
|
|
return (0);
|
|
else if (ISSET(iflag, ICRNL))
|
|
c = '\n';
|
|
} else if (c == '\n' && ISSET(iflag, INLCR))
|
|
c = '\r';
|
|
}
|
|
if (!ISSET(tp->t_lflag, EXTPROC) && ISSET(lflag, ICANON)) {
|
|
/*
|
|
* From here on down canonical mode character
|
|
* processing takes place.
|
|
*/
|
|
/*
|
|
* erase (^H / ^?)
|
|
*/
|
|
if (CCEQ(cc[VERASE], c)) {
|
|
if (tp->t_rawq.c_cc)
|
|
ttyrub(unputc(&tp->t_rawq), tp);
|
|
goto endcase;
|
|
}
|
|
/*
|
|
* kill (^U)
|
|
*/
|
|
if (CCEQ(cc[VKILL], c)) {
|
|
if (ISSET(lflag, ECHOKE) &&
|
|
tp->t_rawq.c_cc == tp->t_rocount &&
|
|
!ISSET(lflag, ECHOPRT))
|
|
while (tp->t_rawq.c_cc)
|
|
ttyrub(unputc(&tp->t_rawq), tp);
|
|
else {
|
|
ttyecho(c, tp);
|
|
if (ISSET(lflag, ECHOK) ||
|
|
ISSET(lflag, ECHOKE))
|
|
ttyecho('\n', tp);
|
|
FLUSHQ(&tp->t_rawq);
|
|
tp->t_rocount = 0;
|
|
}
|
|
CLR(tp->t_state, TS_LOCAL);
|
|
goto endcase;
|
|
}
|
|
/*
|
|
* word erase (^W)
|
|
*/
|
|
if (CCEQ(cc[VWERASE], c) && ISSET(lflag, IEXTEN)) {
|
|
int ctype;
|
|
|
|
/*
|
|
* erase whitespace
|
|
*/
|
|
while ((c = unputc(&tp->t_rawq)) == ' ' || c == '\t')
|
|
ttyrub(c, tp);
|
|
if (c == -1)
|
|
goto endcase;
|
|
/*
|
|
* erase last char of word and remember the
|
|
* next chars type (for ALTWERASE)
|
|
*/
|
|
ttyrub(c, tp);
|
|
c = unputc(&tp->t_rawq);
|
|
if (c == -1)
|
|
goto endcase;
|
|
if (c == ' ' || c == '\t') {
|
|
(void)putc(c, &tp->t_rawq);
|
|
goto endcase;
|
|
}
|
|
ctype = ISALPHA(c);
|
|
/*
|
|
* erase rest of word
|
|
*/
|
|
do {
|
|
ttyrub(c, tp);
|
|
c = unputc(&tp->t_rawq);
|
|
if (c == -1)
|
|
goto endcase;
|
|
} while (c != ' ' && c != '\t' &&
|
|
(!ISSET(lflag, ALTWERASE) || ISALPHA(c) == ctype));
|
|
(void)putc(c, &tp->t_rawq);
|
|
goto endcase;
|
|
}
|
|
/*
|
|
* reprint line (^R)
|
|
*/
|
|
if (CCEQ(cc[VREPRINT], c) && ISSET(lflag, IEXTEN)) {
|
|
ttyretype(tp);
|
|
goto endcase;
|
|
}
|
|
/*
|
|
* ^T - kernel info and generate SIGINFO
|
|
*/
|
|
if (CCEQ(cc[VSTATUS], c) && ISSET(lflag, IEXTEN)) {
|
|
if (ISSET(lflag, ISIG))
|
|
pgsignal(tp->t_pgrp, SIGINFO, 1);
|
|
if (!ISSET(lflag, NOKERNINFO))
|
|
ttyinfo(tp);
|
|
goto endcase;
|
|
}
|
|
}
|
|
/*
|
|
* Check for input buffer overflow
|
|
*/
|
|
if (tp->t_rawq.c_cc + tp->t_canq.c_cc >= MAX_INPUT) {
|
|
input_overflow:
|
|
if (ISSET(iflag, IMAXBEL)) {
|
|
if (tp->t_outq.c_cc < tp->t_ohiwat)
|
|
(void)ttyoutput(CTRL('g'), tp);
|
|
}
|
|
goto endcase;
|
|
}
|
|
|
|
if ( c == 0377 && ISSET(iflag, PARMRK) && !ISSET(iflag, ISTRIP)
|
|
&& ISSET(iflag, IGNBRK|IGNPAR) != (IGNBRK|IGNPAR))
|
|
(void)putc(0377 | TTY_QUOTE, &tp->t_rawq);
|
|
|
|
/*
|
|
* Put data char in q for user and
|
|
* wakeup on seeing a line delimiter.
|
|
*/
|
|
if (putc(c, &tp->t_rawq) >= 0) {
|
|
if (!ISSET(lflag, ICANON)) {
|
|
ttwakeup(tp);
|
|
ttyecho(c, tp);
|
|
goto endcase;
|
|
}
|
|
if (TTBREAKC(c, lflag)) {
|
|
tp->t_rocount = 0;
|
|
catq(&tp->t_rawq, &tp->t_canq);
|
|
ttwakeup(tp);
|
|
} else if (tp->t_rocount++ == 0)
|
|
tp->t_rocol = tp->t_column;
|
|
if (ISSET(tp->t_state, TS_ERASE)) {
|
|
/*
|
|
* end of prterase \.../
|
|
*/
|
|
CLR(tp->t_state, TS_ERASE);
|
|
(void)ttyoutput('/', tp);
|
|
}
|
|
i = tp->t_column;
|
|
ttyecho(c, tp);
|
|
if (CCEQ(cc[VEOF], c) && ISSET(lflag, ECHO)) {
|
|
/*
|
|
* Place the cursor over the '^' of the ^D.
|
|
*/
|
|
i = imin(2, tp->t_column - i);
|
|
while (i > 0) {
|
|
(void)ttyoutput('\b', tp);
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
endcase:
|
|
/*
|
|
* IXANY means allow any character to restart output.
|
|
*/
|
|
if (ISSET(tp->t_state, TS_TTSTOP) &&
|
|
!ISSET(iflag, IXANY) && cc[VSTART] != cc[VSTOP])
|
|
return (0);
|
|
restartoutput:
|
|
CLR(tp->t_lflag, FLUSHO);
|
|
CLR(tp->t_state, TS_TTSTOP);
|
|
startoutput:
|
|
return (ttstart(tp));
|
|
}
|
|
|
|
/*
|
|
* Output a single character on a tty, doing output processing
|
|
* as needed (expanding tabs, newline processing, etc.).
|
|
* Returns < 0 if succeeds, otherwise returns char to resend.
|
|
* Must be recursive.
|
|
*/
|
|
static int
|
|
ttyoutput(c, tp)
|
|
register int c;
|
|
register struct tty *tp;
|
|
{
|
|
register tcflag_t oflag;
|
|
register int col, s;
|
|
|
|
oflag = tp->t_oflag;
|
|
if (!ISSET(oflag, OPOST)) {
|
|
if (ISSET(tp->t_lflag, FLUSHO))
|
|
return (-1);
|
|
if (putc(c, &tp->t_outq))
|
|
return (c);
|
|
tk_nout++;
|
|
tp->t_outcc++;
|
|
return (-1);
|
|
}
|
|
/*
|
|
* Do tab expansion if OXTABS is set. Special case if we external
|
|
* processing, we don't do the tab expansion because we'll probably
|
|
* get it wrong. If tab expansion needs to be done, let it happen
|
|
* externally.
|
|
*/
|
|
CLR(c, ~TTY_CHARMASK);
|
|
if (c == '\t' &&
|
|
ISSET(oflag, OXTABS) && !ISSET(tp->t_lflag, EXTPROC)) {
|
|
c = 8 - (tp->t_column & 7);
|
|
if (!ISSET(tp->t_lflag, FLUSHO)) {
|
|
s = spltty(); /* Don't interrupt tabs. */
|
|
c -= b_to_q(" ", c, &tp->t_outq);
|
|
tk_nout += c;
|
|
tp->t_outcc += c;
|
|
splx(s);
|
|
}
|
|
tp->t_column += c;
|
|
return (c ? -1 : '\t');
|
|
}
|
|
if (c == CEOT && ISSET(oflag, ONOEOT))
|
|
return (-1);
|
|
|
|
/*
|
|
* Newline translation: if ONLCR is set,
|
|
* translate newline into "\r\n".
|
|
*/
|
|
if (c == '\n' && ISSET(tp->t_oflag, ONLCR)) {
|
|
tk_nout++;
|
|
tp->t_outcc++;
|
|
if (putc('\r', &tp->t_outq))
|
|
return (c);
|
|
}
|
|
tk_nout++;
|
|
tp->t_outcc++;
|
|
if (!ISSET(tp->t_lflag, FLUSHO) && putc(c, &tp->t_outq))
|
|
return (c);
|
|
|
|
col = tp->t_column;
|
|
switch (CCLASS(c)) {
|
|
case BACKSPACE:
|
|
if (col > 0)
|
|
--col;
|
|
break;
|
|
case CONTROL:
|
|
break;
|
|
case NEWLINE:
|
|
case RETURN:
|
|
col = 0;
|
|
break;
|
|
case ORDINARY:
|
|
++col;
|
|
break;
|
|
case TAB:
|
|
col = (col + 8) & ~7;
|
|
break;
|
|
}
|
|
tp->t_column = col;
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Ioctls for all tty devices. Called after line-discipline specific ioctl
|
|
* has been called to do discipline-specific functions and/or reject any
|
|
* of these ioctl commands.
|
|
*/
|
|
/* ARGSUSED */
|
|
int
|
|
ttioctl(tp, cmd, data, flag)
|
|
register struct tty *tp;
|
|
u_long cmd;
|
|
int flag;
|
|
void *data;
|
|
{
|
|
register struct proc *p;
|
|
int s, error;
|
|
|
|
p = curproc; /* XXX */
|
|
|
|
/* If the ioctl involves modification, hang if in the background. */
|
|
switch (cmd) {
|
|
case TIOCFLUSH:
|
|
case TIOCSETA:
|
|
case TIOCSETD:
|
|
case TIOCSETAF:
|
|
case TIOCSETAW:
|
|
#ifdef notdef
|
|
case TIOCSPGRP:
|
|
#endif
|
|
case TIOCSTAT:
|
|
case TIOCSTI:
|
|
case TIOCSWINSZ:
|
|
#if defined(COMPAT_43) || defined(COMPAT_SUNOS)
|
|
case TIOCLBIC:
|
|
case TIOCLBIS:
|
|
case TIOCLSET:
|
|
case TIOCSETC:
|
|
case OTIOCSETD:
|
|
case TIOCSETN:
|
|
case TIOCSETP:
|
|
case TIOCSLTC:
|
|
#endif
|
|
while (isbackground(p, tp) &&
|
|
(p->p_flag & P_PPWAIT) == 0 &&
|
|
(p->p_sigignore & sigmask(SIGTTOU)) == 0 &&
|
|
(p->p_sigmask & sigmask(SIGTTOU)) == 0) {
|
|
if (p->p_pgrp->pg_jobc == 0)
|
|
return (EIO);
|
|
pgsignal(p->p_pgrp, SIGTTOU, 1);
|
|
error = ttysleep(tp, &lbolt, TTOPRI | PCATCH, "ttybg1",
|
|
0);
|
|
if (error)
|
|
return (error);
|
|
}
|
|
break;
|
|
}
|
|
|
|
switch (cmd) { /* Process the ioctl. */
|
|
case FIOASYNC: /* set/clear async i/o */
|
|
s = spltty();
|
|
if (*(int *)data)
|
|
SET(tp->t_state, TS_ASYNC);
|
|
else
|
|
CLR(tp->t_state, TS_ASYNC);
|
|
splx(s);
|
|
break;
|
|
case FIONBIO: /* set/clear non-blocking i/o */
|
|
break; /* XXX: delete. */
|
|
case FIONREAD: /* get # bytes to read */
|
|
s = spltty();
|
|
*(int *)data = ttnread(tp);
|
|
splx(s);
|
|
break;
|
|
|
|
case FIOSETOWN:
|
|
/*
|
|
* Policy -- Don't allow FIOSETOWN on someone else's
|
|
* controlling tty
|
|
*/
|
|
if (tp->t_session != NULL && !isctty(p, tp))
|
|
return (ENOTTY);
|
|
|
|
error = fsetown(*(int *)data, &tp->t_sigio);
|
|
if (error)
|
|
return (error);
|
|
break;
|
|
case FIOGETOWN:
|
|
if (tp->t_session != NULL && !isctty(p, tp))
|
|
return (ENOTTY);
|
|
*(int *)data = fgetown(tp->t_sigio);
|
|
break;
|
|
|
|
case TIOCEXCL: /* set exclusive use of tty */
|
|
s = spltty();
|
|
SET(tp->t_state, TS_XCLUDE);
|
|
splx(s);
|
|
break;
|
|
case TIOCFLUSH: { /* flush buffers */
|
|
register int flags = *(int *)data;
|
|
|
|
if (flags == 0)
|
|
flags = FREAD | FWRITE;
|
|
else
|
|
flags &= FREAD | FWRITE;
|
|
ttyflush(tp, flags);
|
|
break;
|
|
}
|
|
case TIOCCONS: /* become virtual console */
|
|
if (*(int *)data) {
|
|
if (constty && constty != tp &&
|
|
ISSET(constty->t_state, TS_CONNECTED))
|
|
return (EBUSY);
|
|
#ifndef UCONSOLE
|
|
if (error = suser(p->p_ucred, &p->p_acflag))
|
|
return (error);
|
|
#endif
|
|
constty = tp;
|
|
} else if (tp == constty)
|
|
constty = NULL;
|
|
break;
|
|
case TIOCDRAIN: /* wait till output drained */
|
|
error = ttywait(tp);
|
|
if (error)
|
|
return (error);
|
|
break;
|
|
case TIOCGETA: { /* get termios struct */
|
|
struct termios *t = (struct termios *)data;
|
|
|
|
bcopy(&tp->t_termios, t, sizeof(struct termios));
|
|
break;
|
|
}
|
|
case TIOCGETD: /* get line discipline */
|
|
*(int *)data = tp->t_line;
|
|
break;
|
|
case TIOCGWINSZ: /* get window size */
|
|
*(struct winsize *)data = tp->t_winsize;
|
|
break;
|
|
case TIOCGPGRP: /* get pgrp of tty */
|
|
if (!isctty(p, tp))
|
|
return (ENOTTY);
|
|
*(int *)data = tp->t_pgrp ? tp->t_pgrp->pg_id : NO_PID;
|
|
break;
|
|
#ifdef TIOCHPCL
|
|
case TIOCHPCL: /* hang up on last close */
|
|
s = spltty();
|
|
SET(tp->t_cflag, HUPCL);
|
|
splx(s);
|
|
break;
|
|
#endif
|
|
case TIOCNXCL: /* reset exclusive use of tty */
|
|
s = spltty();
|
|
CLR(tp->t_state, TS_XCLUDE);
|
|
splx(s);
|
|
break;
|
|
case TIOCOUTQ: /* output queue size */
|
|
*(int *)data = tp->t_outq.c_cc;
|
|
break;
|
|
case TIOCSETA: /* set termios struct */
|
|
case TIOCSETAW: /* drain output, set */
|
|
case TIOCSETAF: { /* drn out, fls in, set */
|
|
register struct termios *t = (struct termios *)data;
|
|
|
|
if (t->c_ispeed < 0 || t->c_ospeed < 0)
|
|
return (EINVAL);
|
|
s = spltty();
|
|
if (cmd == TIOCSETAW || cmd == TIOCSETAF) {
|
|
error = ttywait(tp);
|
|
if (error) {
|
|
splx(s);
|
|
return (error);
|
|
}
|
|
if (cmd == TIOCSETAF)
|
|
ttyflush(tp, FREAD);
|
|
}
|
|
if (!ISSET(t->c_cflag, CIGNORE)) {
|
|
/*
|
|
* Set device hardware.
|
|
*/
|
|
if (tp->t_param && (error = (*tp->t_param)(tp, t))) {
|
|
splx(s);
|
|
return (error);
|
|
}
|
|
if (ISSET(t->c_cflag, CLOCAL) &&
|
|
!ISSET(tp->t_cflag, CLOCAL)) {
|
|
/*
|
|
* XXX disconnections would be too hard to
|
|
* get rid of without this kludge. The only
|
|
* way to get rid of controlling terminals
|
|
* is to exit from the session leader.
|
|
*/
|
|
CLR(tp->t_state, TS_ZOMBIE);
|
|
|
|
wakeup(TSA_CARR_ON(tp));
|
|
ttwakeup(tp);
|
|
ttwwakeup(tp);
|
|
}
|
|
if ((ISSET(tp->t_state, TS_CARR_ON) ||
|
|
ISSET(t->c_cflag, CLOCAL)) &&
|
|
!ISSET(tp->t_state, TS_ZOMBIE))
|
|
SET(tp->t_state, TS_CONNECTED);
|
|
else
|
|
CLR(tp->t_state, TS_CONNECTED);
|
|
tp->t_cflag = t->c_cflag;
|
|
tp->t_ispeed = t->c_ispeed;
|
|
tp->t_ospeed = t->c_ospeed;
|
|
ttsetwater(tp);
|
|
}
|
|
if (ISSET(t->c_lflag, ICANON) != ISSET(tp->t_lflag, ICANON) &&
|
|
cmd != TIOCSETAF) {
|
|
if (ISSET(t->c_lflag, ICANON))
|
|
SET(tp->t_lflag, PENDIN);
|
|
else {
|
|
/*
|
|
* XXX we really shouldn't allow toggling
|
|
* ICANON while we're in a non-termios line
|
|
* discipline. Now we have to worry about
|
|
* panicing for a null queue.
|
|
*/
|
|
if (tp->t_canq.c_cbreserved > 0 &&
|
|
tp->t_rawq.c_cbreserved > 0) {
|
|
catq(&tp->t_rawq, &tp->t_canq);
|
|
/*
|
|
* XXX the queue limits may be
|
|
* different, so the old queue
|
|
* swapping method no longer works.
|
|
*/
|
|
catq(&tp->t_canq, &tp->t_rawq);
|
|
}
|
|
CLR(tp->t_lflag, PENDIN);
|
|
}
|
|
ttwakeup(tp);
|
|
}
|
|
tp->t_iflag = t->c_iflag;
|
|
tp->t_oflag = t->c_oflag;
|
|
/*
|
|
* Make the EXTPROC bit read only.
|
|
*/
|
|
if (ISSET(tp->t_lflag, EXTPROC))
|
|
SET(t->c_lflag, EXTPROC);
|
|
else
|
|
CLR(t->c_lflag, EXTPROC);
|
|
tp->t_lflag = t->c_lflag | ISSET(tp->t_lflag, PENDIN);
|
|
if (t->c_cc[VMIN] != tp->t_cc[VMIN] ||
|
|
t->c_cc[VTIME] != tp->t_cc[VTIME])
|
|
ttwakeup(tp);
|
|
bcopy(t->c_cc, tp->t_cc, sizeof(t->c_cc));
|
|
splx(s);
|
|
break;
|
|
}
|
|
case TIOCSETD: { /* set line discipline */
|
|
register int t = *(int *)data;
|
|
dev_t device = tp->t_dev;
|
|
|
|
if ((u_int)t >= nlinesw)
|
|
return (ENXIO);
|
|
if (t != tp->t_line) {
|
|
s = spltty();
|
|
(*linesw[tp->t_line].l_close)(tp, flag);
|
|
error = (*linesw[t].l_open)(device, tp);
|
|
if (error) {
|
|
(void)(*linesw[tp->t_line].l_open)(device, tp);
|
|
splx(s);
|
|
return (error);
|
|
}
|
|
tp->t_line = t;
|
|
splx(s);
|
|
}
|
|
break;
|
|
}
|
|
case TIOCSTART: /* start output, like ^Q */
|
|
s = spltty();
|
|
if (ISSET(tp->t_state, TS_TTSTOP) ||
|
|
ISSET(tp->t_lflag, FLUSHO)) {
|
|
CLR(tp->t_lflag, FLUSHO);
|
|
CLR(tp->t_state, TS_TTSTOP);
|
|
ttstart(tp);
|
|
}
|
|
splx(s);
|
|
break;
|
|
case TIOCSTI: /* simulate terminal input */
|
|
if (p->p_ucred->cr_uid && (flag & FREAD) == 0)
|
|
return (EPERM);
|
|
if (p->p_ucred->cr_uid && !isctty(p, tp))
|
|
return (EACCES);
|
|
s = spltty();
|
|
(*linesw[tp->t_line].l_rint)(*(u_char *)data, tp);
|
|
splx(s);
|
|
break;
|
|
case TIOCSTOP: /* stop output, like ^S */
|
|
s = spltty();
|
|
if (!ISSET(tp->t_state, TS_TTSTOP)) {
|
|
SET(tp->t_state, TS_TTSTOP);
|
|
#ifdef sun4c /* XXX */
|
|
(*tp->t_stop)(tp, 0);
|
|
#else
|
|
(*cdevsw[major(tp->t_dev)]->d_stop)(tp, 0);
|
|
#endif
|
|
}
|
|
splx(s);
|
|
break;
|
|
case TIOCSCTTY: /* become controlling tty */
|
|
/* Session ctty vnode pointer set in vnode layer. */
|
|
if (!SESS_LEADER(p) ||
|
|
((p->p_session->s_ttyvp || tp->t_session) &&
|
|
(tp->t_session != p->p_session)))
|
|
return (EPERM);
|
|
tp->t_session = p->p_session;
|
|
tp->t_pgrp = p->p_pgrp;
|
|
p->p_session->s_ttyp = tp;
|
|
p->p_flag |= P_CONTROLT;
|
|
break;
|
|
case TIOCSPGRP: { /* set pgrp of tty */
|
|
register struct pgrp *pgrp = pgfind(*(int *)data);
|
|
|
|
if (!isctty(p, tp))
|
|
return (ENOTTY);
|
|
else if (pgrp == NULL || pgrp->pg_session != p->p_session)
|
|
return (EPERM);
|
|
tp->t_pgrp = pgrp;
|
|
break;
|
|
}
|
|
case TIOCSTAT: /* simulate control-T */
|
|
s = spltty();
|
|
ttyinfo(tp);
|
|
splx(s);
|
|
break;
|
|
case TIOCSWINSZ: /* set window size */
|
|
if (bcmp((caddr_t)&tp->t_winsize, data,
|
|
sizeof (struct winsize))) {
|
|
tp->t_winsize = *(struct winsize *)data;
|
|
pgsignal(tp->t_pgrp, SIGWINCH, 1);
|
|
}
|
|
break;
|
|
case TIOCSDRAINWAIT:
|
|
error = suser(p->p_ucred, &p->p_acflag);
|
|
if (error)
|
|
return (error);
|
|
tp->t_timeout = *(int *)data * hz;
|
|
wakeup(TSA_OCOMPLETE(tp));
|
|
wakeup(TSA_OLOWAT(tp));
|
|
break;
|
|
case TIOCGDRAINWAIT:
|
|
*(int *)data = tp->t_timeout / hz;
|
|
break;
|
|
default:
|
|
#if defined(COMPAT_43) || defined(COMPAT_SUNOS)
|
|
return (ttcompat(tp, cmd, data, flag));
|
|
#else
|
|
return (ENOIOCTL);
|
|
#endif
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
ttypoll(tp, events, p)
|
|
struct tty *tp;
|
|
int events;
|
|
struct proc *p;
|
|
{
|
|
int s;
|
|
int revents = 0;
|
|
|
|
if (tp == NULL) /* XXX used to return ENXIO, but that means true! */
|
|
return ((events & (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM))
|
|
| POLLHUP);
|
|
|
|
s = spltty();
|
|
if (events & (POLLIN | POLLRDNORM))
|
|
if (ttnread(tp) > 0 || ISSET(tp->t_state, TS_ZOMBIE))
|
|
revents |= events & (POLLIN | POLLRDNORM);
|
|
else
|
|
selrecord(p, &tp->t_rsel);
|
|
|
|
if (events & (POLLOUT | POLLWRNORM))
|
|
if ((tp->t_outq.c_cc <= tp->t_olowat &&
|
|
ISSET(tp->t_state, TS_CONNECTED))
|
|
|| ISSET(tp->t_state, TS_ZOMBIE))
|
|
revents |= events & (POLLOUT | POLLWRNORM);
|
|
else
|
|
selrecord(p, &tp->t_wsel);
|
|
splx(s);
|
|
return (revents);
|
|
}
|
|
|
|
/*
|
|
* This is a wrapper for compatibility with the select vector used by
|
|
* cdevsw. It relies on a proper xxxdevtotty routine.
|
|
*/
|
|
int
|
|
ttpoll(dev, events, p)
|
|
dev_t dev;
|
|
int events;
|
|
struct proc *p;
|
|
{
|
|
return ttypoll((*cdevsw[major(dev)]->d_devtotty)(dev), events, p);
|
|
}
|
|
|
|
/*
|
|
* Must be called at spltty().
|
|
*/
|
|
static int
|
|
ttnread(tp)
|
|
struct tty *tp;
|
|
{
|
|
int nread;
|
|
|
|
if (ISSET(tp->t_lflag, PENDIN))
|
|
ttypend(tp);
|
|
nread = tp->t_canq.c_cc;
|
|
if (!ISSET(tp->t_lflag, ICANON)) {
|
|
nread += tp->t_rawq.c_cc;
|
|
if (nread < tp->t_cc[VMIN] && tp->t_cc[VTIME] == 0)
|
|
nread = 0;
|
|
}
|
|
return (nread);
|
|
}
|
|
|
|
/*
|
|
* Wait for output to drain.
|
|
*/
|
|
int
|
|
ttywait(tp)
|
|
register struct tty *tp;
|
|
{
|
|
int error, s;
|
|
|
|
error = 0;
|
|
s = spltty();
|
|
while ((tp->t_outq.c_cc || ISSET(tp->t_state, TS_BUSY)) &&
|
|
ISSET(tp->t_state, TS_CONNECTED) && tp->t_oproc) {
|
|
(*tp->t_oproc)(tp);
|
|
if ((tp->t_outq.c_cc || ISSET(tp->t_state, TS_BUSY)) &&
|
|
ISSET(tp->t_state, TS_CONNECTED)) {
|
|
SET(tp->t_state, TS_SO_OCOMPLETE);
|
|
error = ttysleep(tp, TSA_OCOMPLETE(tp),
|
|
TTOPRI | PCATCH, "ttywai",
|
|
tp->t_timeout);
|
|
if (error) {
|
|
if (error == EWOULDBLOCK)
|
|
error = EIO;
|
|
break;
|
|
}
|
|
} else
|
|
break;
|
|
}
|
|
if (!error && (tp->t_outq.c_cc || ISSET(tp->t_state, TS_BUSY)))
|
|
error = EIO;
|
|
splx(s);
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Flush if successfully wait.
|
|
*/
|
|
static int
|
|
ttywflush(tp)
|
|
struct tty *tp;
|
|
{
|
|
int error;
|
|
|
|
if ((error = ttywait(tp)) == 0)
|
|
ttyflush(tp, FREAD);
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Flush tty read and/or write queues, notifying anyone waiting.
|
|
*/
|
|
void
|
|
ttyflush(tp, rw)
|
|
register struct tty *tp;
|
|
int rw;
|
|
{
|
|
register int s;
|
|
|
|
s = spltty();
|
|
#if 0
|
|
again:
|
|
#endif
|
|
if (rw & FWRITE) {
|
|
FLUSHQ(&tp->t_outq);
|
|
CLR(tp->t_state, TS_TTSTOP);
|
|
}
|
|
#ifdef sun4c /* XXX */
|
|
(*tp->t_stop)(tp, rw);
|
|
#else
|
|
(*cdevsw[major(tp->t_dev)]->d_stop)(tp, rw);
|
|
#endif
|
|
if (rw & FREAD) {
|
|
FLUSHQ(&tp->t_canq);
|
|
FLUSHQ(&tp->t_rawq);
|
|
CLR(tp->t_lflag, PENDIN);
|
|
tp->t_rocount = 0;
|
|
tp->t_rocol = 0;
|
|
CLR(tp->t_state, TS_LOCAL);
|
|
ttwakeup(tp);
|
|
if (ISSET(tp->t_state, TS_TBLOCK)) {
|
|
if (rw & FWRITE)
|
|
FLUSHQ(&tp->t_outq);
|
|
ttyunblock(tp);
|
|
|
|
/*
|
|
* Don't let leave any state that might clobber the
|
|
* next line discipline (although we should do more
|
|
* to send the START char). Not clearing the state
|
|
* may have caused the "putc to a clist with no
|
|
* reserved cblocks" panic/printf.
|
|
*/
|
|
CLR(tp->t_state, TS_TBLOCK);
|
|
|
|
#if 0 /* forget it, sleeping isn't always safe and we don't know when it is */
|
|
if (ISSET(tp->t_iflag, IXOFF)) {
|
|
/*
|
|
* XXX wait a bit in the hope that the stop
|
|
* character (if any) will go out. Waiting
|
|
* isn't good since it allows races. This
|
|
* will be fixed when the stop character is
|
|
* put in a special queue. Don't bother with
|
|
* the checks in ttywait() since the timeout
|
|
* will save us.
|
|
*/
|
|
SET(tp->t_state, TS_SO_OCOMPLETE);
|
|
ttysleep(tp, TSA_OCOMPLETE(tp), TTOPRI,
|
|
"ttyfls", hz / 10);
|
|
/*
|
|
* Don't try sending the stop character again.
|
|
*/
|
|
CLR(tp->t_state, TS_TBLOCK);
|
|
goto again;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
if (rw & FWRITE) {
|
|
FLUSHQ(&tp->t_outq);
|
|
ttwwakeup(tp);
|
|
}
|
|
splx(s);
|
|
}
|
|
|
|
/*
|
|
* Copy in the default termios characters.
|
|
*/
|
|
void
|
|
termioschars(t)
|
|
struct termios *t;
|
|
{
|
|
|
|
bcopy(ttydefchars, t->c_cc, sizeof t->c_cc);
|
|
}
|
|
|
|
/*
|
|
* Old interface.
|
|
*/
|
|
void
|
|
ttychars(tp)
|
|
struct tty *tp;
|
|
{
|
|
|
|
termioschars(&tp->t_termios);
|
|
}
|
|
|
|
/*
|
|
* Handle input high water. Send stop character for the IXOFF case. Turn
|
|
* on our input flow control bit and propagate the changes to the driver.
|
|
* XXX the stop character should be put in a special high priority queue.
|
|
*/
|
|
void
|
|
ttyblock(tp)
|
|
struct tty *tp;
|
|
{
|
|
|
|
SET(tp->t_state, TS_TBLOCK);
|
|
if (ISSET(tp->t_iflag, IXOFF) && tp->t_cc[VSTOP] != _POSIX_VDISABLE &&
|
|
putc(tp->t_cc[VSTOP], &tp->t_outq) != 0)
|
|
CLR(tp->t_state, TS_TBLOCK); /* try again later */
|
|
ttstart(tp);
|
|
}
|
|
|
|
/*
|
|
* Handle input low water. Send start character for the IXOFF case. Turn
|
|
* off our input flow control bit and propagate the changes to the driver.
|
|
* XXX the start character should be put in a special high priority queue.
|
|
*/
|
|
static void
|
|
ttyunblock(tp)
|
|
struct tty *tp;
|
|
{
|
|
|
|
CLR(tp->t_state, TS_TBLOCK);
|
|
if (ISSET(tp->t_iflag, IXOFF) && tp->t_cc[VSTART] != _POSIX_VDISABLE &&
|
|
putc(tp->t_cc[VSTART], &tp->t_outq) != 0)
|
|
SET(tp->t_state, TS_TBLOCK); /* try again later */
|
|
ttstart(tp);
|
|
}
|
|
|
|
#ifdef notyet
|
|
/* Not used by any current (i386) drivers. */
|
|
/*
|
|
* Restart after an inter-char delay.
|
|
*/
|
|
void
|
|
ttrstrt(tp_arg)
|
|
void *tp_arg;
|
|
{
|
|
struct tty *tp;
|
|
int s;
|
|
|
|
#ifdef DIAGNOSTIC
|
|
if (tp_arg == NULL)
|
|
panic("ttrstrt");
|
|
#endif
|
|
tp = tp_arg;
|
|
s = spltty();
|
|
|
|
CLR(tp->t_state, TS_TIMEOUT);
|
|
ttstart(tp);
|
|
|
|
splx(s);
|
|
}
|
|
#endif
|
|
|
|
int
|
|
ttstart(tp)
|
|
struct tty *tp;
|
|
{
|
|
|
|
if (tp->t_oproc != NULL) /* XXX: Kludge for pty. */
|
|
(*tp->t_oproc)(tp);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* "close" a line discipline
|
|
*/
|
|
int
|
|
ttylclose(tp, flag)
|
|
struct tty *tp;
|
|
int flag;
|
|
{
|
|
|
|
if (flag & FNONBLOCK || ttywflush(tp))
|
|
ttyflush(tp, FREAD | FWRITE);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Handle modem control transition on a tty.
|
|
* Flag indicates new state of carrier.
|
|
* Returns 0 if the line should be turned off, otherwise 1.
|
|
*/
|
|
int
|
|
ttymodem(tp, flag)
|
|
register struct tty *tp;
|
|
int flag;
|
|
{
|
|
|
|
if (ISSET(tp->t_state, TS_CARR_ON) && ISSET(tp->t_cflag, MDMBUF)) {
|
|
/*
|
|
* MDMBUF: do flow control according to carrier flag
|
|
* XXX TS_CAR_OFLOW doesn't do anything yet. TS_TTSTOP
|
|
* works if IXON and IXANY are clear.
|
|
*/
|
|
if (flag) {
|
|
CLR(tp->t_state, TS_CAR_OFLOW);
|
|
CLR(tp->t_state, TS_TTSTOP);
|
|
ttstart(tp);
|
|
} else if (!ISSET(tp->t_state, TS_CAR_OFLOW)) {
|
|
SET(tp->t_state, TS_CAR_OFLOW);
|
|
SET(tp->t_state, TS_TTSTOP);
|
|
#ifdef sun4c /* XXX */
|
|
(*tp->t_stop)(tp, 0);
|
|
#else
|
|
(*cdevsw[major(tp->t_dev)]->d_stop)(tp, 0);
|
|
#endif
|
|
}
|
|
} else if (flag == 0) {
|
|
/*
|
|
* Lost carrier.
|
|
*/
|
|
CLR(tp->t_state, TS_CARR_ON);
|
|
if (ISSET(tp->t_state, TS_ISOPEN) &&
|
|
!ISSET(tp->t_cflag, CLOCAL)) {
|
|
SET(tp->t_state, TS_ZOMBIE);
|
|
CLR(tp->t_state, TS_CONNECTED);
|
|
if (tp->t_session && tp->t_session->s_leader)
|
|
psignal(tp->t_session->s_leader, SIGHUP);
|
|
ttyflush(tp, FREAD | FWRITE);
|
|
return (0);
|
|
}
|
|
} else {
|
|
/*
|
|
* Carrier now on.
|
|
*/
|
|
SET(tp->t_state, TS_CARR_ON);
|
|
if (!ISSET(tp->t_state, TS_ZOMBIE))
|
|
SET(tp->t_state, TS_CONNECTED);
|
|
wakeup(TSA_CARR_ON(tp));
|
|
ttwakeup(tp);
|
|
ttwwakeup(tp);
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* Reinput pending characters after state switch
|
|
* call at spltty().
|
|
*/
|
|
static void
|
|
ttypend(tp)
|
|
register struct tty *tp;
|
|
{
|
|
struct clist tq;
|
|
register int c;
|
|
|
|
CLR(tp->t_lflag, PENDIN);
|
|
SET(tp->t_state, TS_TYPEN);
|
|
/*
|
|
* XXX this assumes too much about clist internals. It may even
|
|
* fail if the cblock slush pool is empty. We can't allocate more
|
|
* cblocks here because we are called from an interrupt handler
|
|
* and clist_alloc_cblocks() can wait.
|
|
*/
|
|
tq = tp->t_rawq;
|
|
bzero(&tp->t_rawq, sizeof tp->t_rawq);
|
|
tp->t_rawq.c_cbmax = tq.c_cbmax;
|
|
tp->t_rawq.c_cbreserved = tq.c_cbreserved;
|
|
while ((c = getc(&tq)) >= 0)
|
|
ttyinput(c, tp);
|
|
CLR(tp->t_state, TS_TYPEN);
|
|
}
|
|
|
|
/*
|
|
* Process a read call on a tty device.
|
|
*/
|
|
int
|
|
ttread(tp, uio, flag)
|
|
register struct tty *tp;
|
|
struct uio *uio;
|
|
int flag;
|
|
{
|
|
register struct clist *qp;
|
|
register int c;
|
|
register tcflag_t lflag;
|
|
register cc_t *cc = tp->t_cc;
|
|
register struct proc *p = curproc;
|
|
int s, first, error = 0;
|
|
int has_stime = 0, last_cc = 0;
|
|
long slp = 0; /* XXX this should be renamed `timo'. */
|
|
|
|
loop:
|
|
s = spltty();
|
|
lflag = tp->t_lflag;
|
|
/*
|
|
* take pending input first
|
|
*/
|
|
if (ISSET(lflag, PENDIN)) {
|
|
ttypend(tp);
|
|
splx(s); /* reduce latency */
|
|
s = spltty();
|
|
lflag = tp->t_lflag; /* XXX ttypend() clobbers it */
|
|
}
|
|
|
|
/*
|
|
* Hang process if it's in the background.
|
|
*/
|
|
if (isbackground(p, tp)) {
|
|
splx(s);
|
|
if ((p->p_sigignore & sigmask(SIGTTIN)) ||
|
|
(p->p_sigmask & sigmask(SIGTTIN)) ||
|
|
p->p_flag & P_PPWAIT || p->p_pgrp->pg_jobc == 0)
|
|
return (EIO);
|
|
pgsignal(p->p_pgrp, SIGTTIN, 1);
|
|
error = ttysleep(tp, &lbolt, TTIPRI | PCATCH, "ttybg2", 0);
|
|
if (error)
|
|
return (error);
|
|
goto loop;
|
|
}
|
|
|
|
if (ISSET(tp->t_state, TS_ZOMBIE)) {
|
|
splx(s);
|
|
return (0); /* EOF */
|
|
}
|
|
|
|
/*
|
|
* If canonical, use the canonical queue,
|
|
* else use the raw queue.
|
|
*
|
|
* (should get rid of clists...)
|
|
*/
|
|
qp = ISSET(lflag, ICANON) ? &tp->t_canq : &tp->t_rawq;
|
|
|
|
if (flag & IO_NDELAY) {
|
|
if (qp->c_cc > 0)
|
|
goto read;
|
|
if (!ISSET(lflag, ICANON) && cc[VMIN] == 0) {
|
|
splx(s);
|
|
return (0);
|
|
}
|
|
splx(s);
|
|
return (EWOULDBLOCK);
|
|
}
|
|
if (!ISSET(lflag, ICANON)) {
|
|
int m = cc[VMIN];
|
|
long t = cc[VTIME];
|
|
struct timeval stime, timecopy;
|
|
|
|
/*
|
|
* Check each of the four combinations.
|
|
* (m > 0 && t == 0) is the normal read case.
|
|
* It should be fairly efficient, so we check that and its
|
|
* companion case (m == 0 && t == 0) first.
|
|
* For the other two cases, we compute the target sleep time
|
|
* into slp.
|
|
*/
|
|
if (t == 0) {
|
|
if (qp->c_cc < m)
|
|
goto sleep;
|
|
if (qp->c_cc > 0)
|
|
goto read;
|
|
|
|
/* m, t and qp->c_cc are all 0. 0 is enough input. */
|
|
splx(s);
|
|
return (0);
|
|
}
|
|
t *= 100000; /* time in us */
|
|
#define diff(t1, t2) (((t1).tv_sec - (t2).tv_sec) * 1000000 + \
|
|
((t1).tv_usec - (t2).tv_usec))
|
|
if (m > 0) {
|
|
if (qp->c_cc <= 0)
|
|
goto sleep;
|
|
if (qp->c_cc >= m)
|
|
goto read;
|
|
getmicrotime(&timecopy);
|
|
if (!has_stime) {
|
|
/* first character, start timer */
|
|
has_stime = 1;
|
|
stime = timecopy;
|
|
slp = t;
|
|
} else if (qp->c_cc > last_cc) {
|
|
/* got a character, restart timer */
|
|
stime = timecopy;
|
|
slp = t;
|
|
} else {
|
|
/* nothing, check expiration */
|
|
slp = t - diff(timecopy, stime);
|
|
if (slp <= 0)
|
|
goto read;
|
|
}
|
|
last_cc = qp->c_cc;
|
|
} else { /* m == 0 */
|
|
if (qp->c_cc > 0)
|
|
goto read;
|
|
getmicrotime(&timecopy);
|
|
if (!has_stime) {
|
|
has_stime = 1;
|
|
stime = timecopy;
|
|
slp = t;
|
|
} else {
|
|
slp = t - diff(timecopy, stime);
|
|
if (slp <= 0) {
|
|
/* Timed out, but 0 is enough input. */
|
|
splx(s);
|
|
return (0);
|
|
}
|
|
}
|
|
}
|
|
#undef diff
|
|
/*
|
|
* Rounding down may make us wake up just short
|
|
* of the target, so we round up.
|
|
* The formula is ceiling(slp * hz/1000000).
|
|
* 32-bit arithmetic is enough for hz < 169.
|
|
* XXX see tvtohz() for how to avoid overflow if hz
|
|
* is large (divide by `tick' and/or arrange to
|
|
* use tvtohz() if hz is large).
|
|
*/
|
|
slp = (long) (((u_long)slp * hz) + 999999) / 1000000;
|
|
goto sleep;
|
|
}
|
|
if (qp->c_cc <= 0) {
|
|
sleep:
|
|
/*
|
|
* There is no input, or not enough input and we can block.
|
|
*/
|
|
error = ttysleep(tp, TSA_HUP_OR_INPUT(tp), TTIPRI | PCATCH,
|
|
ISSET(tp->t_state, TS_CONNECTED) ?
|
|
"ttyin" : "ttyhup", (int)slp);
|
|
splx(s);
|
|
if (error == EWOULDBLOCK)
|
|
error = 0;
|
|
else if (error)
|
|
return (error);
|
|
/*
|
|
* XXX what happens if another process eats some input
|
|
* while we are asleep (not just here)? It would be
|
|
* safest to detect changes and reset our state variables
|
|
* (has_stime and last_cc).
|
|
*/
|
|
slp = 0;
|
|
goto loop;
|
|
}
|
|
read:
|
|
splx(s);
|
|
/*
|
|
* Input present, check for input mapping and processing.
|
|
*/
|
|
first = 1;
|
|
if (ISSET(lflag, ICANON | ISIG))
|
|
goto slowcase;
|
|
for (;;) {
|
|
char ibuf[IBUFSIZ];
|
|
int icc;
|
|
|
|
icc = imin(uio->uio_resid, IBUFSIZ);
|
|
icc = q_to_b(qp, ibuf, icc);
|
|
if (icc <= 0) {
|
|
if (first)
|
|
goto loop;
|
|
break;
|
|
}
|
|
error = uiomove(ibuf, icc, uio);
|
|
/*
|
|
* XXX if there was an error then we should ungetc() the
|
|
* unmoved chars and reduce icc here.
|
|
*/
|
|
#if NSNP > 0
|
|
if (ISSET(tp->t_lflag, ECHO) &&
|
|
ISSET(tp->t_state, TS_SNOOP) && tp->t_sc != NULL)
|
|
snpin((struct snoop *)tp->t_sc, ibuf, icc);
|
|
#endif
|
|
if (error)
|
|
break;
|
|
if (uio->uio_resid == 0)
|
|
break;
|
|
first = 0;
|
|
}
|
|
goto out;
|
|
slowcase:
|
|
for (;;) {
|
|
c = getc(qp);
|
|
if (c < 0) {
|
|
if (first)
|
|
goto loop;
|
|
break;
|
|
}
|
|
/*
|
|
* delayed suspend (^Y)
|
|
*/
|
|
if (CCEQ(cc[VDSUSP], c) &&
|
|
ISSET(lflag, IEXTEN | ISIG) == (IEXTEN | ISIG)) {
|
|
pgsignal(tp->t_pgrp, SIGTSTP, 1);
|
|
if (first) {
|
|
error = ttysleep(tp, &lbolt, TTIPRI | PCATCH,
|
|
"ttybg3", 0);
|
|
if (error)
|
|
break;
|
|
goto loop;
|
|
}
|
|
break;
|
|
}
|
|
/*
|
|
* Interpret EOF only in canonical mode.
|
|
*/
|
|
if (CCEQ(cc[VEOF], c) && ISSET(lflag, ICANON))
|
|
break;
|
|
/*
|
|
* Give user character.
|
|
*/
|
|
error = ureadc(c, uio);
|
|
if (error)
|
|
/* XXX should ungetc(c, qp). */
|
|
break;
|
|
#if NSNP > 0
|
|
/*
|
|
* Only snoop directly on input in echo mode. Non-echoed
|
|
* input will be snooped later iff the application echoes it.
|
|
*/
|
|
if (ISSET(tp->t_lflag, ECHO) &&
|
|
ISSET(tp->t_state, TS_SNOOP) && tp->t_sc != NULL)
|
|
snpinc((struct snoop *)tp->t_sc, (char)c);
|
|
#endif
|
|
if (uio->uio_resid == 0)
|
|
break;
|
|
/*
|
|
* In canonical mode check for a "break character"
|
|
* marking the end of a "line of input".
|
|
*/
|
|
if (ISSET(lflag, ICANON) && TTBREAKC(c, lflag))
|
|
break;
|
|
first = 0;
|
|
}
|
|
|
|
out:
|
|
/*
|
|
* Look to unblock input now that (presumably)
|
|
* the input queue has gone down.
|
|
*/
|
|
s = spltty();
|
|
if (ISSET(tp->t_state, TS_TBLOCK) &&
|
|
tp->t_rawq.c_cc + tp->t_canq.c_cc <= tp->t_ilowat)
|
|
ttyunblock(tp);
|
|
splx(s);
|
|
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Check the output queue on tp for space for a kernel message (from uprintf
|
|
* or tprintf). Allow some space over the normal hiwater mark so we don't
|
|
* lose messages due to normal flow control, but don't let the tty run amok.
|
|
* Sleeps here are not interruptible, but we return prematurely if new signals
|
|
* arrive.
|
|
*/
|
|
int
|
|
ttycheckoutq(tp, wait)
|
|
register struct tty *tp;
|
|
int wait;
|
|
{
|
|
int hiwat, s, oldsig;
|
|
|
|
hiwat = tp->t_ohiwat;
|
|
s = spltty();
|
|
oldsig = wait ? curproc->p_siglist : 0;
|
|
if (tp->t_outq.c_cc > hiwat + OBUFSIZ + 100)
|
|
while (tp->t_outq.c_cc > hiwat) {
|
|
ttstart(tp);
|
|
if (tp->t_outq.c_cc <= hiwat)
|
|
break;
|
|
if (wait == 0 || curproc->p_siglist != oldsig) {
|
|
splx(s);
|
|
return (0);
|
|
}
|
|
SET(tp->t_state, TS_SO_OLOWAT);
|
|
tsleep(TSA_OLOWAT(tp), PZERO - 1, "ttoutq", hz);
|
|
}
|
|
splx(s);
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* Process a write call on a tty device.
|
|
*/
|
|
int
|
|
ttwrite(tp, uio, flag)
|
|
register struct tty *tp;
|
|
register struct uio *uio;
|
|
int flag;
|
|
{
|
|
register char *cp = NULL;
|
|
register int cc, ce;
|
|
register struct proc *p;
|
|
int i, hiwat, cnt, error, s;
|
|
char obuf[OBUFSIZ];
|
|
|
|
hiwat = tp->t_ohiwat;
|
|
cnt = uio->uio_resid;
|
|
error = 0;
|
|
cc = 0;
|
|
loop:
|
|
s = spltty();
|
|
if (ISSET(tp->t_state, TS_ZOMBIE)) {
|
|
splx(s);
|
|
if (uio->uio_resid == cnt)
|
|
error = EIO;
|
|
goto out;
|
|
}
|
|
if (!ISSET(tp->t_state, TS_CONNECTED)) {
|
|
if (flag & IO_NDELAY) {
|
|
splx(s);
|
|
error = EWOULDBLOCK;
|
|
goto out;
|
|
}
|
|
error = ttysleep(tp, TSA_CARR_ON(tp), TTIPRI | PCATCH,
|
|
"ttydcd", 0);
|
|
splx(s);
|
|
if (error)
|
|
goto out;
|
|
goto loop;
|
|
}
|
|
splx(s);
|
|
/*
|
|
* Hang the process if it's in the background.
|
|
*/
|
|
p = curproc;
|
|
if (isbackground(p, tp) &&
|
|
ISSET(tp->t_lflag, TOSTOP) && (p->p_flag & P_PPWAIT) == 0 &&
|
|
(p->p_sigignore & sigmask(SIGTTOU)) == 0 &&
|
|
(p->p_sigmask & sigmask(SIGTTOU)) == 0) {
|
|
if (p->p_pgrp->pg_jobc == 0) {
|
|
error = EIO;
|
|
goto out;
|
|
}
|
|
pgsignal(p->p_pgrp, SIGTTOU, 1);
|
|
error = ttysleep(tp, &lbolt, TTIPRI | PCATCH, "ttybg4", 0);
|
|
if (error)
|
|
goto out;
|
|
goto loop;
|
|
}
|
|
/*
|
|
* Process the user's data in at most OBUFSIZ chunks. Perform any
|
|
* output translation. Keep track of high water mark, sleep on
|
|
* overflow awaiting device aid in acquiring new space.
|
|
*/
|
|
while (uio->uio_resid > 0 || cc > 0) {
|
|
if (ISSET(tp->t_lflag, FLUSHO)) {
|
|
uio->uio_resid = 0;
|
|
return (0);
|
|
}
|
|
if (tp->t_outq.c_cc > hiwat)
|
|
goto ovhiwat;
|
|
/*
|
|
* Grab a hunk of data from the user, unless we have some
|
|
* leftover from last time.
|
|
*/
|
|
if (cc == 0) {
|
|
cc = imin(uio->uio_resid, OBUFSIZ);
|
|
cp = obuf;
|
|
error = uiomove(cp, cc, uio);
|
|
if (error) {
|
|
cc = 0;
|
|
break;
|
|
}
|
|
#if NSNP > 0
|
|
if (ISSET(tp->t_state, TS_SNOOP) && tp->t_sc != NULL)
|
|
snpin((struct snoop *)tp->t_sc, cp, cc);
|
|
#endif
|
|
}
|
|
/*
|
|
* If nothing fancy need be done, grab those characters we
|
|
* can handle without any of ttyoutput's processing and
|
|
* just transfer them to the output q. For those chars
|
|
* which require special processing (as indicated by the
|
|
* bits in char_type), call ttyoutput. After processing
|
|
* a hunk of data, look for FLUSHO so ^O's will take effect
|
|
* immediately.
|
|
*/
|
|
while (cc > 0) {
|
|
if (!ISSET(tp->t_oflag, OPOST))
|
|
ce = cc;
|
|
else {
|
|
ce = cc - scanc((u_int)cc, (u_char *)cp,
|
|
char_type, CCLASSMASK);
|
|
/*
|
|
* If ce is zero, then we're processing
|
|
* a special character through ttyoutput.
|
|
*/
|
|
if (ce == 0) {
|
|
tp->t_rocount = 0;
|
|
if (ttyoutput(*cp, tp) >= 0) {
|
|
/* No Clists, wait a bit. */
|
|
ttstart(tp);
|
|
if (flag & IO_NDELAY) {
|
|
error = EWOULDBLOCK;
|
|
goto out;
|
|
}
|
|
error = ttysleep(tp, &lbolt,
|
|
TTOPRI|PCATCH,
|
|
"ttybf1", 0);
|
|
if (error)
|
|
goto out;
|
|
goto loop;
|
|
}
|
|
cp++;
|
|
cc--;
|
|
if (ISSET(tp->t_lflag, FLUSHO) ||
|
|
tp->t_outq.c_cc > hiwat)
|
|
goto ovhiwat;
|
|
continue;
|
|
}
|
|
}
|
|
/*
|
|
* A bunch of normal characters have been found.
|
|
* Transfer them en masse to the output queue and
|
|
* continue processing at the top of the loop.
|
|
* If there are any further characters in this
|
|
* <= OBUFSIZ chunk, the first should be a character
|
|
* requiring special handling by ttyoutput.
|
|
*/
|
|
tp->t_rocount = 0;
|
|
i = b_to_q(cp, ce, &tp->t_outq);
|
|
ce -= i;
|
|
tp->t_column += ce;
|
|
cp += ce, cc -= ce, tk_nout += ce;
|
|
tp->t_outcc += ce;
|
|
if (i > 0) {
|
|
/* No Clists, wait a bit. */
|
|
ttstart(tp);
|
|
if (flag & IO_NDELAY) {
|
|
error = EWOULDBLOCK;
|
|
goto out;
|
|
}
|
|
error = ttysleep(tp, &lbolt, TTOPRI | PCATCH,
|
|
"ttybf2", 0);
|
|
if (error)
|
|
goto out;
|
|
goto loop;
|
|
}
|
|
if (ISSET(tp->t_lflag, FLUSHO) ||
|
|
tp->t_outq.c_cc > hiwat)
|
|
break;
|
|
}
|
|
ttstart(tp);
|
|
}
|
|
out:
|
|
/*
|
|
* If cc is nonzero, we leave the uio structure inconsistent, as the
|
|
* offset and iov pointers have moved forward, but it doesn't matter
|
|
* (the call will either return short or restart with a new uio).
|
|
*/
|
|
uio->uio_resid += cc;
|
|
return (error);
|
|
|
|
ovhiwat:
|
|
ttstart(tp);
|
|
s = spltty();
|
|
/*
|
|
* This can only occur if FLUSHO is set in t_lflag,
|
|
* or if ttstart/oproc is synchronous (or very fast).
|
|
*/
|
|
if (tp->t_outq.c_cc <= hiwat) {
|
|
splx(s);
|
|
goto loop;
|
|
}
|
|
if (flag & IO_NDELAY) {
|
|
splx(s);
|
|
uio->uio_resid += cc;
|
|
return (uio->uio_resid == cnt ? EWOULDBLOCK : 0);
|
|
}
|
|
SET(tp->t_state, TS_SO_OLOWAT);
|
|
error = ttysleep(tp, TSA_OLOWAT(tp), TTOPRI | PCATCH, "ttywri",
|
|
tp->t_timeout);
|
|
splx(s);
|
|
if (error == EWOULDBLOCK)
|
|
error = EIO;
|
|
if (error)
|
|
goto out;
|
|
goto loop;
|
|
}
|
|
|
|
/*
|
|
* Rubout one character from the rawq of tp
|
|
* as cleanly as possible.
|
|
*/
|
|
static void
|
|
ttyrub(c, tp)
|
|
register int c;
|
|
register struct tty *tp;
|
|
{
|
|
register char *cp;
|
|
register int savecol;
|
|
int tabc, s;
|
|
|
|
if (!ISSET(tp->t_lflag, ECHO) || ISSET(tp->t_lflag, EXTPROC))
|
|
return;
|
|
CLR(tp->t_lflag, FLUSHO);
|
|
if (ISSET(tp->t_lflag, ECHOE)) {
|
|
if (tp->t_rocount == 0) {
|
|
/*
|
|
* Screwed by ttwrite; retype
|
|
*/
|
|
ttyretype(tp);
|
|
return;
|
|
}
|
|
if (c == ('\t' | TTY_QUOTE) || c == ('\n' | TTY_QUOTE))
|
|
ttyrubo(tp, 2);
|
|
else {
|
|
CLR(c, ~TTY_CHARMASK);
|
|
switch (CCLASS(c)) {
|
|
case ORDINARY:
|
|
ttyrubo(tp, 1);
|
|
break;
|
|
case BACKSPACE:
|
|
case CONTROL:
|
|
case NEWLINE:
|
|
case RETURN:
|
|
case VTAB:
|
|
if (ISSET(tp->t_lflag, ECHOCTL))
|
|
ttyrubo(tp, 2);
|
|
break;
|
|
case TAB:
|
|
if (tp->t_rocount < tp->t_rawq.c_cc) {
|
|
ttyretype(tp);
|
|
return;
|
|
}
|
|
s = spltty();
|
|
savecol = tp->t_column;
|
|
SET(tp->t_state, TS_CNTTB);
|
|
SET(tp->t_lflag, FLUSHO);
|
|
tp->t_column = tp->t_rocol;
|
|
cp = tp->t_rawq.c_cf;
|
|
if (cp)
|
|
tabc = *cp; /* XXX FIX NEXTC */
|
|
for (; cp; cp = nextc(&tp->t_rawq, cp, &tabc))
|
|
ttyecho(tabc, tp);
|
|
CLR(tp->t_lflag, FLUSHO);
|
|
CLR(tp->t_state, TS_CNTTB);
|
|
splx(s);
|
|
|
|
/* savecol will now be length of the tab. */
|
|
savecol -= tp->t_column;
|
|
tp->t_column += savecol;
|
|
if (savecol > 8)
|
|
savecol = 8; /* overflow screw */
|
|
while (--savecol >= 0)
|
|
(void)ttyoutput('\b', tp);
|
|
break;
|
|
default: /* XXX */
|
|
#define PANICSTR "ttyrub: would panic c = %d, val = %d\n"
|
|
(void)printf(PANICSTR, c, CCLASS(c));
|
|
#ifdef notdef
|
|
panic(PANICSTR, c, CCLASS(c));
|
|
#endif
|
|
}
|
|
}
|
|
} else if (ISSET(tp->t_lflag, ECHOPRT)) {
|
|
if (!ISSET(tp->t_state, TS_ERASE)) {
|
|
SET(tp->t_state, TS_ERASE);
|
|
(void)ttyoutput('\\', tp);
|
|
}
|
|
ttyecho(c, tp);
|
|
} else
|
|
ttyecho(tp->t_cc[VERASE], tp);
|
|
--tp->t_rocount;
|
|
}
|
|
|
|
/*
|
|
* Back over cnt characters, erasing them.
|
|
*/
|
|
static void
|
|
ttyrubo(tp, cnt)
|
|
register struct tty *tp;
|
|
int cnt;
|
|
{
|
|
|
|
while (cnt-- > 0) {
|
|
(void)ttyoutput('\b', tp);
|
|
(void)ttyoutput(' ', tp);
|
|
(void)ttyoutput('\b', tp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ttyretype --
|
|
* Reprint the rawq line. Note, it is assumed that c_cc has already
|
|
* been checked.
|
|
*/
|
|
static void
|
|
ttyretype(tp)
|
|
register struct tty *tp;
|
|
{
|
|
register char *cp;
|
|
int s, c;
|
|
|
|
/* Echo the reprint character. */
|
|
if (tp->t_cc[VREPRINT] != _POSIX_VDISABLE)
|
|
ttyecho(tp->t_cc[VREPRINT], tp);
|
|
|
|
(void)ttyoutput('\n', tp);
|
|
|
|
/*
|
|
* XXX
|
|
* FIX: NEXTC IS BROKEN - DOESN'T CHECK QUOTE
|
|
* BIT OF FIRST CHAR.
|
|
*/
|
|
s = spltty();
|
|
for (cp = tp->t_canq.c_cf, c = (cp != NULL ? *cp : 0);
|
|
cp != NULL; cp = nextc(&tp->t_canq, cp, &c))
|
|
ttyecho(c, tp);
|
|
for (cp = tp->t_rawq.c_cf, c = (cp != NULL ? *cp : 0);
|
|
cp != NULL; cp = nextc(&tp->t_rawq, cp, &c))
|
|
ttyecho(c, tp);
|
|
CLR(tp->t_state, TS_ERASE);
|
|
splx(s);
|
|
|
|
tp->t_rocount = tp->t_rawq.c_cc;
|
|
tp->t_rocol = 0;
|
|
}
|
|
|
|
/*
|
|
* Echo a typed character to the terminal.
|
|
*/
|
|
static void
|
|
ttyecho(c, tp)
|
|
register int c;
|
|
register struct tty *tp;
|
|
{
|
|
|
|
if (!ISSET(tp->t_state, TS_CNTTB))
|
|
CLR(tp->t_lflag, FLUSHO);
|
|
if ((!ISSET(tp->t_lflag, ECHO) &&
|
|
(c != '\n' || !ISSET(tp->t_lflag, ECHONL))) ||
|
|
ISSET(tp->t_lflag, EXTPROC))
|
|
return;
|
|
if (ISSET(tp->t_lflag, ECHOCTL) &&
|
|
((ISSET(c, TTY_CHARMASK) <= 037 && c != '\t' && c != '\n') ||
|
|
ISSET(c, TTY_CHARMASK) == 0177)) {
|
|
(void)ttyoutput('^', tp);
|
|
CLR(c, ~TTY_CHARMASK);
|
|
if (c == 0177)
|
|
c = '?';
|
|
else
|
|
c += 'A' - 1;
|
|
}
|
|
(void)ttyoutput(c, tp);
|
|
}
|
|
|
|
/*
|
|
* Wake up any readers on a tty.
|
|
*/
|
|
void
|
|
ttwakeup(tp)
|
|
register struct tty *tp;
|
|
{
|
|
|
|
if (tp->t_rsel.si_pid != 0)
|
|
selwakeup(&tp->t_rsel);
|
|
if (ISSET(tp->t_state, TS_ASYNC) && tp->t_sigio != NULL)
|
|
pgsigio(tp->t_sigio, SIGIO, (tp->t_session != NULL));
|
|
wakeup(TSA_HUP_OR_INPUT(tp));
|
|
}
|
|
|
|
/*
|
|
* Wake up any writers on a tty.
|
|
*/
|
|
void
|
|
ttwwakeup(tp)
|
|
register struct tty *tp;
|
|
{
|
|
|
|
if (tp->t_wsel.si_pid != 0 && tp->t_outq.c_cc <= tp->t_olowat)
|
|
selwakeup(&tp->t_wsel);
|
|
if (ISSET(tp->t_state, TS_BUSY | TS_SO_OCOMPLETE) ==
|
|
TS_SO_OCOMPLETE && tp->t_outq.c_cc == 0) {
|
|
CLR(tp->t_state, TS_SO_OCOMPLETE);
|
|
wakeup(TSA_OCOMPLETE(tp));
|
|
}
|
|
if (ISSET(tp->t_state, TS_SO_OLOWAT) &&
|
|
tp->t_outq.c_cc <= tp->t_olowat) {
|
|
CLR(tp->t_state, TS_SO_OLOWAT);
|
|
wakeup(TSA_OLOWAT(tp));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Look up a code for a specified speed in a conversion table;
|
|
* used by drivers to map software speed values to hardware parameters.
|
|
*/
|
|
int
|
|
ttspeedtab(speed, table)
|
|
int speed;
|
|
register struct speedtab *table;
|
|
{
|
|
|
|
for ( ; table->sp_speed != -1; table++)
|
|
if (table->sp_speed == speed)
|
|
return (table->sp_code);
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Set input and output watermarks and buffer sizes. For input, the
|
|
* high watermark is about one second's worth of input above empty, the
|
|
* low watermark is slightly below high water, and the buffer size is a
|
|
* driver-dependent amount above high water. For output, the watermarks
|
|
* are near the ends of the buffer, with about 1 second's worth of input
|
|
* between them. All this only applies to the standard line discipline.
|
|
*/
|
|
void
|
|
ttsetwater(tp)
|
|
struct tty *tp;
|
|
{
|
|
register int cps, ttmaxhiwat, x;
|
|
|
|
/* Input. */
|
|
clist_alloc_cblocks(&tp->t_canq, TTYHOG, 512);
|
|
switch (tp->t_ispeedwat) {
|
|
case (speed_t)-1:
|
|
cps = tp->t_ispeed / 10;
|
|
break;
|
|
case 0:
|
|
/*
|
|
* This case is for old drivers that don't know about
|
|
* t_ispeedwat. Arrange for them to get the old buffer
|
|
* sizes and watermarks.
|
|
*/
|
|
cps = TTYHOG - 2 * 256;
|
|
tp->t_ififosize = 2 * 256;
|
|
break;
|
|
default:
|
|
cps = tp->t_ispeedwat / 10;
|
|
break;
|
|
}
|
|
tp->t_ihiwat = cps;
|
|
tp->t_ilowat = 7 * cps / 8;
|
|
x = cps + tp->t_ififosize;
|
|
clist_alloc_cblocks(&tp->t_rawq, x, x);
|
|
|
|
/* Output. */
|
|
switch (tp->t_ospeedwat) {
|
|
case (speed_t)-1:
|
|
cps = tp->t_ospeed / 10;
|
|
ttmaxhiwat = 2 * TTMAXHIWAT;
|
|
break;
|
|
case 0:
|
|
cps = tp->t_ospeed / 10;
|
|
ttmaxhiwat = TTMAXHIWAT;
|
|
break;
|
|
default:
|
|
cps = tp->t_ospeedwat / 10;
|
|
ttmaxhiwat = 8 * TTMAXHIWAT;
|
|
break;
|
|
}
|
|
#define CLAMP(x, h, l) ((x) > h ? h : ((x) < l) ? l : (x))
|
|
tp->t_olowat = x = CLAMP(cps / 2, TTMAXLOWAT, TTMINLOWAT);
|
|
x += cps;
|
|
x = CLAMP(x, ttmaxhiwat, TTMINHIWAT); /* XXX clamps are too magic */
|
|
tp->t_ohiwat = roundup(x, CBSIZE); /* XXX for compat */
|
|
x = imax(tp->t_ohiwat, TTMAXHIWAT); /* XXX for compat/safety */
|
|
x += OBUFSIZ + 100;
|
|
clist_alloc_cblocks(&tp->t_outq, x, x);
|
|
#undef CLAMP
|
|
}
|
|
|
|
/*
|
|
* Report on state of foreground process group.
|
|
*/
|
|
void
|
|
ttyinfo(tp)
|
|
register struct tty *tp;
|
|
{
|
|
register struct proc *p, *pick;
|
|
struct timeval utime, stime;
|
|
int tmp;
|
|
|
|
if (ttycheckoutq(tp,0) == 0)
|
|
return;
|
|
|
|
/* Print load average. */
|
|
tmp = (averunnable.ldavg[0] * 100 + FSCALE / 2) >> FSHIFT;
|
|
ttyprintf(tp, "load: %d.%02d ", tmp / 100, tmp % 100);
|
|
|
|
if (tp->t_session == NULL)
|
|
ttyprintf(tp, "not a controlling terminal\n");
|
|
else if (tp->t_pgrp == NULL)
|
|
ttyprintf(tp, "no foreground process group\n");
|
|
else if ((p = tp->t_pgrp->pg_members.lh_first) == 0)
|
|
ttyprintf(tp, "empty foreground process group\n");
|
|
else {
|
|
/* Pick interesting process. */
|
|
for (pick = NULL; p != 0; p = p->p_pglist.le_next)
|
|
if (proc_compare(pick, p))
|
|
pick = p;
|
|
|
|
ttyprintf(tp, " cmd: %s %d [%s] ", pick->p_comm, pick->p_pid,
|
|
pick->p_stat == SRUN ? "running" :
|
|
pick->p_wmesg ? pick->p_wmesg : "iowait");
|
|
|
|
calcru(pick, &utime, &stime, NULL);
|
|
|
|
/* Print user time. */
|
|
ttyprintf(tp, "%ld.%02ldu ",
|
|
utime.tv_sec, utime.tv_usec / 10000);
|
|
|
|
/* Print system time. */
|
|
ttyprintf(tp, "%ld.%02lds ",
|
|
stime.tv_sec, stime.tv_usec / 10000);
|
|
|
|
#define pgtok(a) (((a) * PAGE_SIZE) / 1024)
|
|
/* Print percentage cpu, resident set size. */
|
|
tmp = (pick->p_pctcpu * 10000 + FSCALE / 2) >> FSHIFT;
|
|
ttyprintf(tp, "%d%% %ldk\n",
|
|
tmp / 100,
|
|
pick->p_stat == SIDL || pick->p_stat == SZOMB ? 0 :
|
|
#ifdef pmap_resident_count
|
|
(long)pgtok(pmap_resident_count(&pick->p_vmspace->vm_pmap))
|
|
#else
|
|
(long)pgtok(pick->p_vmspace->vm_rssize)
|
|
#endif
|
|
);
|
|
}
|
|
tp->t_rocount = 0; /* so pending input will be retyped if BS */
|
|
}
|
|
|
|
/*
|
|
* Returns 1 if p2 is "better" than p1
|
|
*
|
|
* The algorithm for picking the "interesting" process is thus:
|
|
*
|
|
* 1) Only foreground processes are eligible - implied.
|
|
* 2) Runnable processes are favored over anything else. The runner
|
|
* with the highest cpu utilization is picked (p_estcpu). Ties are
|
|
* broken by picking the highest pid.
|
|
* 3) The sleeper with the shortest sleep time is next. With ties,
|
|
* we pick out just "short-term" sleepers (P_SINTR == 0).
|
|
* 4) Further ties are broken by picking the highest pid.
|
|
*/
|
|
#define ISRUN(p) (((p)->p_stat == SRUN) || ((p)->p_stat == SIDL))
|
|
#define TESTAB(a, b) ((a)<<1 | (b))
|
|
#define ONLYA 2
|
|
#define ONLYB 1
|
|
#define BOTH 3
|
|
|
|
static int
|
|
proc_compare(p1, p2)
|
|
register struct proc *p1, *p2;
|
|
{
|
|
|
|
if (p1 == NULL)
|
|
return (1);
|
|
/*
|
|
* see if at least one of them is runnable
|
|
*/
|
|
switch (TESTAB(ISRUN(p1), ISRUN(p2))) {
|
|
case ONLYA:
|
|
return (0);
|
|
case ONLYB:
|
|
return (1);
|
|
case BOTH:
|
|
/*
|
|
* tie - favor one with highest recent cpu utilization
|
|
*/
|
|
if (p2->p_estcpu > p1->p_estcpu)
|
|
return (1);
|
|
if (p1->p_estcpu > p2->p_estcpu)
|
|
return (0);
|
|
return (p2->p_pid > p1->p_pid); /* tie - return highest pid */
|
|
}
|
|
/*
|
|
* weed out zombies
|
|
*/
|
|
switch (TESTAB(p1->p_stat == SZOMB, p2->p_stat == SZOMB)) {
|
|
case ONLYA:
|
|
return (1);
|
|
case ONLYB:
|
|
return (0);
|
|
case BOTH:
|
|
return (p2->p_pid > p1->p_pid); /* tie - return highest pid */
|
|
}
|
|
/*
|
|
* pick the one with the smallest sleep time
|
|
*/
|
|
if (p2->p_slptime > p1->p_slptime)
|
|
return (0);
|
|
if (p1->p_slptime > p2->p_slptime)
|
|
return (1);
|
|
/*
|
|
* favor one sleeping in a non-interruptible sleep
|
|
*/
|
|
if (p1->p_flag & P_SINTR && (p2->p_flag & P_SINTR) == 0)
|
|
return (1);
|
|
if (p2->p_flag & P_SINTR && (p1->p_flag & P_SINTR) == 0)
|
|
return (0);
|
|
return (p2->p_pid > p1->p_pid); /* tie - return highest pid */
|
|
}
|
|
|
|
/*
|
|
* Output char to tty; console putchar style.
|
|
*/
|
|
int
|
|
tputchar(c, tp)
|
|
int c;
|
|
struct tty *tp;
|
|
{
|
|
register int s;
|
|
|
|
s = spltty();
|
|
if (!ISSET(tp->t_state, TS_CONNECTED)) {
|
|
splx(s);
|
|
return (-1);
|
|
}
|
|
if (c == '\n')
|
|
(void)ttyoutput('\r', tp);
|
|
(void)ttyoutput(c, tp);
|
|
ttstart(tp);
|
|
splx(s);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Sleep on chan, returning ERESTART if tty changed while we napped and
|
|
* returning any errors (e.g. EINTR/EWOULDBLOCK) reported by tsleep. If
|
|
* the tty is revoked, restarting a pending call will redo validation done
|
|
* at the start of the call.
|
|
*/
|
|
int
|
|
ttysleep(tp, chan, pri, wmesg, timo)
|
|
struct tty *tp;
|
|
void *chan;
|
|
int pri, timo;
|
|
char *wmesg;
|
|
{
|
|
int error;
|
|
int gen;
|
|
|
|
gen = tp->t_gen;
|
|
error = tsleep(chan, pri, wmesg, timo);
|
|
if (error)
|
|
return (error);
|
|
return (tp->t_gen == gen ? 0 : ERESTART);
|
|
}
|
|
|
|
#ifdef notyet
|
|
/*
|
|
* XXX this is usable not useful or used. Most tty drivers have
|
|
* ifdefs for using ttymalloc() but assume a different interface.
|
|
*/
|
|
/*
|
|
* Allocate a tty struct. Clists in the struct will be allocated by
|
|
* ttyopen().
|
|
*/
|
|
struct tty *
|
|
ttymalloc()
|
|
{
|
|
struct tty *tp;
|
|
|
|
tp = malloc(sizeof *tp, M_TTYS, M_WAITOK);
|
|
bzero(tp, sizeof *tp);
|
|
return (tp);
|
|
}
|
|
#endif
|
|
|
|
#if 0 /* XXX not yet usable: session leader holds a ref (see kern_exit.c). */
|
|
/*
|
|
* Free a tty struct. Clists in the struct should have been freed by
|
|
* ttyclose().
|
|
*/
|
|
void
|
|
ttyfree(tp)
|
|
struct tty *tp;
|
|
{
|
|
free(tp, M_TTYS);
|
|
}
|
|
#endif /* 0 */
|