Files
Arquivotheca.SunOS-4.1.4/sys/os/tty_ldterm.c
seta75D ff309bfe1c Init
2021-10-11 18:37:13 -03:00

3134 lines
79 KiB
C

#ifndef lint
static char sccsid[] = "@(#)tty_ldterm.c 1.1 94/10/31 SMI";
#endif
/*
* Standard tty streams module.
*/
#include <sys/param.h>
#include <sys/types.h>
#include <sys/termios.h>
#include <sys/termio.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/tty.h>
#include <sys/unistd.h>
#include <sys/user.h>
#include <sys/errno.h>
#include <sys/dk.h>
#include <sys/time.h>
#include <sys/kernel.h>
#include <sys/debug.h>
#define IBSIZE 16 /* "standard" input data block size */
#define OBSIZE 64 /* "standard" output data block size */
#define EBSIZE 16 /* "standard" echo data block size */
#define TTYHOG CANBSIZ /* from param.h; in case anyone uses it */
#define TTXOHI 80
#define TTXOLO 32
/*
* Should be "void", not "int" - they return no values!
*/
static int ldtermopen(/*queue_t *q, int dev, int oflag, int sflag*/);
static int ldtermclose(/*queue_t *q, int flag*/);
static int ldtermrput(/*queue_t *q, mblk_t *mp*/);
static int ldtermrsrv(/*queue_t *q*/);
static int ldtermwput(/*queue_t *q, mblk_t *mp*/);
static int ldtermwsrv(/*queue_t *q*/);
/*
* Since most of the buffering occurs either at the stream head or in
* the "message currently being assembled" buffer, we have a relatively
* small input queue, so that blockages above us get reflected fairly
* quickly to the module below us. We also have a small maximum packet
* size, since you can put a message of that size on an empty queue no
* matter how much bigger than the high water mark it is.
*/
static struct module_info ldtermmiinfo = {
0x0bad, /* to the bone */
"ldterm",
0,
128, /* never eat anything bigger than your head */
500,
200
};
static struct qinit ldtermrinit = {
ldtermrput,
ldtermrsrv,
ldtermopen,
ldtermclose,
NULL,
&ldtermmiinfo
};
/*
* Write side flow control strategy: We want the module to behave as much as
* possible as if it had no write service procedure. Thus, the write put
* procedure will process incoming messages and hand them to the downstream
* module whenever canput allows it. The high water mark is set to one, so
* that only a single message is queued between the time the downstream module
* blocks and this module reflects that state upward.
*/
static struct module_info ldtermmoinfo = {
0x0bad, /* to the bone */
"ldterm",
0,
INFPSZ,
1,
0
};
static struct qinit ldtermwinit = {
ldtermwput,
ldtermwsrv,
ldtermopen,
ldtermclose,
NULL,
&ldtermmoinfo
};
struct streamtab ldtrinfo = {
&ldtermrinit,
&ldtermwinit,
NULL,
NULL,
NULL
};
typedef struct {
mblk_t *t_savbp; /* saved mblk that holds ld struct */
struct termios t_modes; /* modes */
unsigned long t_state; /* internal state of tty module */
int t_line; /* output line of tty */
int t_col; /* output column of tty */
int t_rocount; /* number of chars echoed since last output */
int t_rocol; /* column in which first such char appeared */
mblk_t *t_message; /* pointer to 1st mblk in message being built */
mblk_t *t_endmsg; /* pointer to last mblk in that message */
int t_msglen; /* number of characters in that message */
mblk_t *t_echomp; /* echoed output being assembled */
int t_iocid; /* ID of ioctl reply being awaited */
int t_wbufcid; /* ID of pending write-side bufcall */
} ldterm_state_t;
/*
* Internal state bits.
*/
#define TS_TTSTOP 0x00000001 /* output stopped by ^S */
#define TS_TBLOCK 0x00000002 /* input stopped by IXOFF mode */
#define TS_QUOT 0x00000004 /* last character input was \ */
#define TS_ERASE 0x00000008 /* within a \.../ for PRTRUB */
#define TS_SLNCH 0x00000010 /* next char svc rtn sees is literal */
#define TS_PLNCH 0x00000020 /* next char put rtn sees is literal */
#define TS_TTCR 0x00000040 /* mapping NL to CR-NL */
#define TS_NOCANPUT 0x00000080 /* canonicalization done by */
/* somebody below us */
#define TS_NOCANSRV 0x00000100
#define TS_RESCAN 0x00000200 /* canonicalization mode changed, */
/* rescan input queue */
#define TS_IOCWAIT 0x00000400 /* waiting for reply to ioctl message */
/*
* Statistics counters, for tuning.
*
* They record, respectively, the number of regular priority messages
* processed directly through the write put procedure and the number queued
* and later processed through the write service procedure.
*
* XXX: Is it worth retaining these counts? Should they be moved into
* ldterm_state_t?
*/
struct ldterm_counts {
int ld_wputmsgs;
int ld_wsrvmsgs;
} ldterm_stats;
#define ldterm_wputmsgs ldterm_stats.ld_wputmsgs
#define ldterm_wsrvmsgs ldterm_stats.ld_wsrvmsgs
/*
* Character types.
*/
#define PRINTABLE 0 /* ordinary printable character */
#define CONTROL 1 /* non-special control character */
#define BACKSPACE 2 /* BS */
#define NEWLINE 3 /* NL */
#define TAB 4 /* TAB */
#define VTAB 5 /* VT */
#define RETURN 6 /* CR */
/*
* Table indicating character classes to tty driver. In particular,
* if the class is PRINTABLE, then the character needs no special
* processing on output.
*
* Characters in the C1 set are all considered CONTROL; this will
* work with terminals that properly use the ANSI/ISO extensions,
* but might cause distress with terminals that put graphics in
* the range 0200-0237. On the other hand, characters in that
* range cause even greater distress to other UNIX terminal drivers....
*/
static char typetab[256] = {
/* 000 */ CONTROL, CONTROL, CONTROL, CONTROL,
/* 004 */ CONTROL, CONTROL, CONTROL, CONTROL,
/* 010 */ BACKSPACE, TAB, NEWLINE, CONTROL,
/* 014 */ VTAB, RETURN, CONTROL, CONTROL,
/* 020 */ CONTROL, CONTROL, CONTROL, CONTROL,
/* 024 */ CONTROL, CONTROL, CONTROL, CONTROL,
/* 030 */ CONTROL, CONTROL, CONTROL, CONTROL,
/* 034 */ CONTROL, CONTROL, CONTROL, CONTROL,
/* 040 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 044 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 050 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 054 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 060 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 064 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 070 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 074 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 100 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 104 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 110 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 114 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 120 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 124 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 130 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 134 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 140 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 144 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 150 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 154 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 160 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 164 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 170 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 174 */ PRINTABLE, PRINTABLE, PRINTABLE, CONTROL,
/* 200 */ CONTROL, CONTROL, CONTROL, CONTROL,
/* 204 */ CONTROL, CONTROL, CONTROL, CONTROL,
/* 210 */ CONTROL, CONTROL, CONTROL, CONTROL,
/* 214 */ CONTROL, CONTROL, CONTROL, CONTROL,
/* 220 */ CONTROL, CONTROL, CONTROL, CONTROL,
/* 224 */ CONTROL, CONTROL, CONTROL, CONTROL,
/* 230 */ CONTROL, CONTROL, CONTROL, CONTROL,
/* 234 */ CONTROL, CONTROL, CONTROL, CONTROL,
/* 240 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 244 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 250 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 254 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 260 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 264 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 270 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 274 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 300 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 304 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 310 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 314 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 320 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 324 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 330 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 334 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 340 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 344 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 350 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 354 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 360 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 364 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 370 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
/* 374 */ PRINTABLE, PRINTABLE, PRINTABLE, PRINTABLE,
};
/*
* Translation table for output without OLCUC. All PRINTABLE-class characters
* translate to themselves. All other characters have a zero in the table,
* which stops the copying.
*/
static unsigned char notrantab[256] = {
/* 000 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 010 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 020 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 030 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 040 */ ' ', '!', '"', '#', '$', '%', '&', '\'',
/* 050 */ '(', ')', '*', '+', ',', '-', '.', '/',
/* 060 */ '0', '1', '2', '3', '4', '5', '6', '7',
/* 070 */ '8', '9', ':', ';', '<', '=', '>', '?',
/* 100 */ '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
/* 110 */ 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
/* 120 */ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
/* 130 */ 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
/* 140 */ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
/* 150 */ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
/* 160 */ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
/* 170 */ 'x', 'y', 'z', '{', '|', '}', '~', 0,
/* 200 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 210 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 220 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 230 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 240 */ '\240', '\241', '\242', '\243', '\244', '\245', '\246', '\247',
/* 250 */ '\250', '\251', '\252', '\253', '\254', '\255', '\256', '\257',
/* 260 */ '\260', '\261', '\262', '\263', '\264', '\265', '\266', '\267',
/* 270 */ '\270', '\271', '\272', '\273', '\274', '\275', '\276', '\277',
/* 300 */ '\300', '\301', '\302', '\303', '\304', '\305', '\306', '\307',
/* 310 */ '\310', '\311', '\312', '\313', '\314', '\315', '\316', '\317',
/* 320 */ '\320', '\321', '\322', '\323', '\324', '\325', '\326', '\327',
/* 330 */ '\330', '\331', '\332', '\333', '\334', '\335', '\336', '\337',
/* 340 */ '\340', '\341', '\342', '\343', '\344', '\345', '\346', '\347',
/* 350 */ '\350', '\351', '\352', '\353', '\354', '\355', '\356', '\357',
/* 360 */ '\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367',
/* 370 */ '\370', '\371', '\372', '\373', '\374', '\375', '\376', '\377',
};
/*
* Translation table for output with OLCUC. All PRINTABLE-class characters
* translate to themselves, except for lower-case letters which translate
* to their upper-case equivalents. All other characters have a zero in
* the table, which stops the copying.
*/
static unsigned char lcuctab[256] = {
/* 000 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 010 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 020 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 030 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 040 */ ' ', '!', '"', '#', '$', '%', '&', '\'',
/* 050 */ '(', ')', '*', '+', ',', '-', '.', '/',
/* 060 */ '0', '1', '2', '3', '4', '5', '6', '7',
/* 070 */ '8', '9', ':', ';', '<', '=', '>', '?',
/* 100 */ '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
/* 110 */ 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
/* 120 */ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
/* 130 */ 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
/* 140 */ '`', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
/* 150 */ 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
/* 160 */ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
/* 170 */ 'X', 'Y', 'Z', '{', '|', '}', '~', 0,
/* 200 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 210 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 220 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 230 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 240 */ '\240', '\241', '\242', '\243', '\244', '\245', '\246', '\247',
/* 250 */ '\250', '\251', '\252', '\253', '\254', '\255', '\256', '\257',
/* 260 */ '\260', '\261', '\262', '\263', '\264', '\265', '\266', '\267',
/* 270 */ '\270', '\271', '\272', '\273', '\274', '\275', '\276', '\277',
/* 300 */ '\300', '\301', '\302', '\303', '\304', '\305', '\306', '\307',
/* 310 */ '\310', '\311', '\312', '\313', '\314', '\315', '\316', '\317',
/* 320 */ '\320', '\321', '\322', '\323', '\324', '\325', '\326', '\327',
/* 330 */ '\330', '\331', '\332', '\333', '\334', '\335', '\336', '\337',
/* 340 */ '\340', '\341', '\342', '\343', '\344', '\345', '\346', '\347',
/* 350 */ '\350', '\351', '\352', '\353', '\354', '\355', '\356', '\357',
/* 360 */ '\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367',
/* 370 */ '\370', '\371', '\372', '\373', '\374', '\375', '\376', '\377',
};
/*
* Input mapping table -- if an entry is non-zero, and XCASE is set,
* when the corresponding character is typed preceded by "\" the escape
* sequence is replaced by the table value. Mostly used for
* upper-case only terminals.
*/
static char imaptab[256] = {
/* 000 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 010 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 020 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 030 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 040 */ 0, '|', 0, 0, 0, 0, 0, '`',
/* 050 */ '{', '}', 0, 0, 0, 0, 0, 0,
/* 060 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 070 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 100 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 110 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 120 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 130 */ 0, 0, 0, 0, '\\', 0, '~', 0,
/* 140 */ 0, 'A', 'B', 'C', 'D', 'E', 'F', 'G',
/* 150 */ 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
/* 160 */ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
/* 170 */ 'X', 'Y', 'Z', 0, 0, 0, 0, 0,
/* 200-377 aren't mapped */
};
/*
* Output mapping table -- if an entry is non-zero, and XCASE is set,
* the corresponding character is printed as "\" followed by the table
* value. Mostly used for upper-case only terminals.
*/
static char omaptab[256] = {
/* 000 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 010 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 020 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 030 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 040 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 050 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 060 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 070 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 100 */ 0, 'A', 'B', 'C', 'D', 'E', 'F', 'G',
/* 110 */ 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
/* 120 */ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
/* 130 */ 'X', 'Y', 'Z', 0, 0, 0, 0, 0,
/* 140 */ '\'', 0, 0, 0, 0, 0, 0, 0,
/* 150 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 160 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 170 */ 0, 0, 0, '(', '!', ')', '^', 0,
/* 200-377 aren't mapped */
};
static int ldtermopen_wakeup(/*caddr_t addr*/);
static mblk_t *do_canon_input(/*unsigned char c, mblk_t *bpt, int ebsize,
ldterm_state_t *tp*/);
static int curline_unget(/*ldterm_state_t *tp*/);
static void curline_trim(/*ldterm_state_t *tp*/);
static void curline_rubout(/*unsigned char c, queue_t *q, int ebsize,
ldterm_state_t *tp*/);
static int curline_tabcols(/*ldterm_state_t *tp*/);
static void curline_erase(/*queue_t *q, int ebsize, ldterm_state_t *tp*/);
static void curline_werase(/*queue_t *q, int ebsize, ldterm_state_t *tp*/);
static void curline_kill(/*queue_t *q, int ebsize, ldterm_state_t *tp*/);
static void curline_reprint(/*queue_t *q, int ebsize,
ldterm_state_t *tp*/);
static void do_noncanon_input(/*mblk_t *bp, queue_t *q,
ldterm_state_t *tp*/);
static int echo_char(/*unsigned char c, queue_t *q, int ebsize,
ldterm_state_t *tp*/);
static void output_echo_char(/*unsigned char c, queue_t *q, int bsize,
ldterm_state_t *tp*/);
static void output_echo_string(/*unsigned char *cp, int len, queue_t *q,
int ebsize, ldterm_state_t *tp*/);
static mblk_t *newmsg(/*ldterm_state_t *tp*/);
static void msg_upstream(/*queue_t *q, ldterm_state_t *tp*/);
static int ldterm_wenable(/*long addr*/);
static int ldtermwmsg(/*queue_t *q, mblk_t *mp*/);
static mblk_t *do_output(/*queue_t *q, mblk_t *imp, mblk_t **omp,
ldterm_state_t *tp, int echoing*/);
static void do_flush_output(/*unsigned char c, queue_t *q,
ldterm_state_t *tp*/);
static void do_signal(/*queue_t *q, int sig, unsigned char c, int doecho,
int ignore_noflsh*/);
static void do_ioctl(/*queue_t *q, mblk_t *mp*/);
static void chgstropts(/*struct termios *oldmodep, ldterm_state_t *tp,
queue_t *q*/);
static void ioctl_reply(/*queue_t *q, mblk_t *mp*/);
static struct termios initmodes = {
BRKINT|ICRNL|IXON|ISTRIP, /* iflag */
OPOST|ONLCR|XTABS, /* oflag */
0, /* cflag */
ISIG|ICANON|ECHO|IEXTEN, /* lflag */
0, /* line */
{ CINTR,
CQUIT,
CERASE,
CKILL,
CEOF,
CEOL,
CEOL2,
CSWTCH,
CSTART,
CSTOP,
CSUSP,
CDSUSP,
CRPRNT,
CFLUSH,
CWERASE,
CLNEXT,
0 /* nonexistent STATUS */
}
};
/*
* Line discipline open.
*/
/*ARGSUSED*/
static int
ldtermopen(q, dev, oflag, sflag)
queue_t *q;
int dev, oflag, sflag;
{
register ldterm_state_t *tp;
register mblk_t *bp;
register int s;
register struct stroptions *strop;
int id;
if (q->q_ptr != NULL)
return (NEWCTTY); /* already attached */
if ((bp = allocb((int)sizeof (ldterm_state_t), BPRI_MED)) == NULL) {
printf("ldtermopen: open fails, can't alloc state structure\n");
return (OPENFAIL);
}
bp->b_wptr += sizeof (ldterm_state_t);
tp = (ldterm_state_t *)bp->b_rptr;
tp->t_savbp = bp;
tp->t_modes = initmodes;
tp->t_state = 0;
tp->t_line = 0;
tp->t_col = 0;
tp->t_rocount = 0;
tp->t_rocol = 0;
tp->t_message = NULL;
tp->t_endmsg = NULL;
tp->t_msglen = 0;
tp->t_echomp = NULL;
tp->t_iocid = 0;
tp->t_wbufcid = 0;
q->q_ptr = (caddr_t)tp;
WR(q)->q_ptr = (caddr_t)tp;
/*
* Find out if the module below us does canonicalization; if so,
* we won't do it ourselves.
*/
while (!putctl1(WR(q)->q_next, M_CTL, MC_CANONQUERY)) {
s = splstr();
id = bufcall(1, BPRI_HI, ldtermopen_wakeup, (long)&q->q_ptr);
if (sleep((caddr_t)&q->q_ptr, STIPRI|PCATCH)) {
unbufcall(id);
/* Dump the state structure, then unlink it */
freeb(tp->t_savbp);
q->q_ptr = NULL;
(void) splx(s);
u.u_error = EINTR;
return (OPENFAIL);
}
(void) splx(s);
}
/*
* Set the high-water and low-water marks on the stream head to values
* appropriate for a terminal. Also disable vmin and vtime
* processing, turn on message-nondiscard mode (as we're in ICANON
* mode), and turn on "old-style NODELAY" mode.
*
* N.B.: Disabling vmin and vtime is a bit tricky. It turns out that
* we really want to send a boolean to the stream head that enables or
* disables them. However, adding such a field to the M_SETOPTS
* message would break compatibility with previous releases.
* Fortunately, there's an out. The so_vmin field is declared as
* ushort, but the only legal values are u_chars. Thus we can use
* "(ushort) -1" as an out of band value to encode the boolean. (This
* trick would have been cleaner had so_vmin been declared as short.)
*/
while ((bp =
allocb((int)sizeof (struct stroptions), BPRI_MED)) == NULL) {
s = splstr();
id = bufcall(sizeof (struct stroptions), BPRI_MED,
ldtermopen_wakeup, (long)&q->q_ptr);
if (sleep((caddr_t)&q->q_ptr, STIPRI|PCATCH)) {
unbufcall(id);
/* Dump the state structure, then unlink it */
freeb(tp->t_savbp);
q->q_ptr = NULL;
(void) splx(s);
u.u_error = EINTR;
return (OPENFAIL);
}
(void) splx(s);
}
strop = (struct stroptions *)bp->b_wptr;
strop->so_flags =
SO_READOPT|SO_HIWAT|SO_LOWAT|SO_VMIN|SO_VTIME|SO_NDELON;
strop->so_readopt = RMSGN;
strop->so_hiwat = 300;
strop->so_lowat = 200;
strop->so_vmin = (ushort) -1;
strop->so_vtime = 0;
bp->b_wptr += sizeof (struct stroptions);
bp->b_datap->db_type = M_SETOPTS;
putnext(q, bp);
return (NEWCTTY); /* this can become a controlling TTY */
}
static int
ldtermopen_wakeup(addr)
long addr;
{
wakeup((caddr_t) addr);
}
/*
* Close this module instance, coordinating with the driver below to preserve
* the line discipline's semantics until all output has drained. If a signal
* occurs during this interval, give up and complete the close immediately.
*/
/*ARGSUSED*/
static int
ldtermclose(q, flag)
register queue_t *q;
int flag;
{
ldterm_state_t *tp = (ldterm_state_t *)q->q_ptr;
register mblk_t *bp;
mblk_t *datap;
register struct iocblk *iocb;
register struct stroptions *strop;
register int s;
int id;
/*
* Reset the high-water and low-water marks on the stream head (?),
* turn on byte-stream mode, and turn off "old-style NODELAY" mode.
*/
while ((bp =
allocb((int)sizeof (struct stroptions), BPRI_MED)) == NULL) {
s = splstr();
id = bufcall(sizeof (struct stroptions), BPRI_MED,
ldtermopen_wakeup, (long)&q->q_ptr);
if (sleep((caddr_t)&q->q_ptr, STIPRI|PCATCH)) {
unbufcall(id);
(void) splx(s);
goto tidy_up;
}
(void) splx(s);
}
strop = (struct stroptions *)bp->b_wptr;
strop->so_flags = SO_READOPT|SO_NDELOFF;
strop->so_readopt = RNORM;
bp->b_wptr += sizeof (struct stroptions);
bp->b_datap->db_type = M_SETOPTS;
putnext(q, bp);
/*
* Wait for output to drain completely from the modules and driver
* below us before completing the close. Doing so guarantees that the
* proper tty semantics -- in particular, flow control processing --
* still apply for the remaining pending output.
*
* We do this by sending a TCSBRK ioctl downstream and waiting for the
* driver's response. (When issued with a nonzero argument, this
* ioctl does nothing other than wait for output to drain, which is
* precisely what we want.)
*/
/*
* Set up the ioctl.
*/
while ((bp = allocb((int)sizeof (struct iocblk), BPRI_MED)) == NULL) {
s = splstr();
id = bufcall(sizeof (struct iocblk), BPRI_MED,
ldtermopen_wakeup, (long)&q->q_ptr);
if (sleep((caddr_t)&q->q_ptr, STIPRI|PCATCH)) {
unbufcall(id);
(void) splx(s);
goto tidy_up;
}
(void) splx(s);
}
while ((datap = allocb((int)sizeof (int), BPRI_MED)) == NULL) {
s = splstr();
id = bufcall(sizeof (int), BPRI_MED,
ldtermopen_wakeup, (long)&q->q_ptr);
if (sleep((caddr_t)&q->q_ptr, STIPRI|PCATCH)) {
unbufcall(id);
(void) splx(s);
freeb(bp);
goto tidy_up;
}
(void) splx(s);
}
iocb = (struct iocblk *)bp->b_wptr;
iocb->ioc_cmd = TCSBRK;
iocb->ioc_uid = 0;
iocb->ioc_gid = 0;
iocb->ioc_id = getiocseqno();
iocb->ioc_count = sizeof (int);
iocb->ioc_error = 0;
iocb->ioc_rval = 0;
bp->b_wptr += (sizeof *iocb)/(sizeof *bp->b_wptr);
bp->b_datap->db_type = M_IOCTL;
*(int*)datap->b_wptr = 1; /* arbitrary nonzero value */
datap->b_wptr += (sizeof (int))/(sizeof *datap->b_wptr);
bp->b_cont = datap;
/*
* Fire it off.
*/
tp->t_state |= TS_IOCWAIT;
tp->t_iocid = iocb->ioc_id;
#ifdef MULTIPROCESSOR
/*
* We should check if ldterm write queue has anything here
* before sending TCSBRK downstream to make sure TCSBRK is always
* the last message. Supposingly, "putq()" can do the job.
* However, on 4m platform, since there is no service thread
* at system initialization time, service routines will not run
* until return to user level which does not occur until "init"
* issues system calls. "init" would sleep here forever. The
* following hack is put in which should work
* based on the assumption that at "init" time, no other
* message will exist on the ldterm write queue.
*/
if (!WR(q)->q_first)
putnext(WR(q), bp);
else
putq(WR(q), bp);
#else
/*
* We should check if ldterm write queue has anything here
* before sending TCSBRK downstream. putq() will do
* the job
*/
putq(WR(q), bp);
#endif
/*
* Now wait for it. Let our read queue put routine wake us up
* when it arrives.
*/
while (tp->t_state & TS_IOCWAIT) {
if (sleep((caddr_t)&tp->t_iocid, STIPRI|PCATCH))
break;
}
tidy_up:
/*
* From here to the end, the routine does not sleep, so it's
* guaranteed to run to completion.
*/
/*
* Restart output; since this module handles ^S and ^Q for most
* drivers, rather than those drivers handling it themselves, any
* subsequent ^Qs won't be able to restart it.
* Do this only if we stopped the output, and do it
* only after the output has drained, so we get flow
* control properly handled for all output that has gone
* before the module was popped.
*/
if (tp->t_state & TS_TTSTOP)
(void) putctl(WR(q)->q_next, M_START);
/*
* If we previously sent a M_STOPI (i.e. tandem mode flow control
* output to the other system), restart the other side.
*/
if (tp->t_state & TS_TBLOCK)
(void) putctl(WR(q)->q_next, M_STARTI);
/*
* Commit to the final stage of the close; beyond this point tp is
* invalid outside of this routine. Arrange for messages arriving
* from below to be shunted off to our upstream neighbor.
*/
q->q_ptr = NULL;
/*
* Cancel outstanding "bufcall" request.
*/
if (tp->t_wbufcid)
unbufcall(tp->t_wbufcid);
/*
* Dismantle state by freeing saved messages and then unlinking our
* per-instance state structure.
*/
if (tp->t_message != NULL)
freemsg(tp->t_message);
freeb(tp->t_savbp);
}
/*
* Put procedure for input from driver end of stream (read queue).
*/
static int
ldtermrput(q, mp)
queue_t *q;
mblk_t *mp;
{
register ldterm_state_t *tp;
register unsigned char c;
queue_t *wrq = WR(q); /* write queue of tty mod */
queue_t *nextq = q->q_next; /* queue below us */
mblk_t *bp;
register unsigned char *readp;
register unsigned char *writep;
struct iocblk *iocp;
tp = (ldterm_state_t *)q->q_ptr;
if (tp == NULL) {
/*
* The message has arrived in the window between ldtermclose
* committing to dismantle our per-instance state and the
* STREAMS framework completing the close. Since we're
* effectively closed, pass it along for the stream head to
* worry about.
*/
putnext(q, mp);
return;
}
switch (mp->b_datap->db_type) {
default:
putq(q, mp);
return;
case M_IOCACK:
case M_IOCNAK:
/*
* If we are doing an "ioctl" ourselves, check if this
* is the reply to that code. If so, wake up the
* "close" routine, and toss the reply, otherwise just
* pass it up.
*/
iocp = (struct iocblk *)mp->b_rptr;
if (!(tp->t_state & TS_IOCWAIT) ||
iocp->ioc_id != tp->t_iocid) {
/*
* This isn't the reply we're looking for. Pass it
* on.
*/
putq(q, mp);
} else {
tp->t_state &= ~TS_IOCWAIT;
wakeup((caddr_t)&tp->t_iocid);
freemsg(mp);
}
return;
case M_BREAK:
if (!(tp->t_modes.c_iflag & IGNBRK)) {
if (tp->t_modes.c_iflag & BRKINT) {
/*
* Needed for POSIX compliance:
* Ignore the NOFLSH flag if on.
* NOFLSH does not stop break-related flushing.
*/
do_signal(q, SIGINT, '\0', 0, 1);
freemsg(mp);
} else {
/*
* Convert the message into an M_DATA message
* containing a byte sequence matching the
* PARMRK setting.
*
* XXX: Sleazy code here; we take advantage of
* compatibility code in allocb that
* forces all requests to a minimum of 4
* bytes.
*/
mp->b_datap->db_type = M_DATA;
mp->b_rptr = mp->b_wptr = mp->b_datap->db_base;
if (tp->t_modes.c_iflag & PARMRK) {
/*
* Report the break as the sequence
* '\377' '\0' '\0'.
*/
*mp->b_wptr++ = '\377';
*mp->b_wptr++ = '\0';
} else {
/*
* Report as '\0'. (Fall through...)
*/
}
*mp->b_wptr++ = '\0';
break;
}
} else
freemsg(mp);
return;
case M_CTL:
switch (*mp->b_rptr) {
case MC_NOCANON:
tp->t_state |= TS_NOCANPUT;
break;
case MC_DOCANON:
tp->t_state &= ~TS_NOCANPUT;
break;
}
putq(q, mp);
return;
case M_DATA:
break;
}
/*
* Flow control: send "start input" message if blocked and
* our queue is below its low water mark.
*/
if ((tp->t_modes.c_iflag & IXOFF) && (tp->t_state & TS_TBLOCK) &&
q->q_count <= TTXOLO) {
tp->t_state &= ~TS_TBLOCK;
(void) putctl(wrq->q_next, M_STARTI);
}
/*
* If somebody below us ("intelligent" communications board,
* pseudo-tty controlled by an editor) is doing
* canonicalization, don't scan it for special characters.
*/
if (tp->t_state & TS_NOCANPUT) {
tk_nin += msgdsize(mp);
putq(q, mp);
return;
}
bp = mp;
do {
readp = bp->b_rptr;
writep = readp;
tk_nin += bp->b_wptr - readp;
if (tp->t_modes.c_iflag & (INLCR|IGNCR|ICRNL|IUCLC|IXON) ||
tp->t_modes.c_lflag & (ISIG|ICANON)) {
/*
* We're doing some sort of non-trivial processing
* of input; look at every character.
*/
while (readp < bp->b_wptr) {
c = *readp++;
if (tp->t_modes.c_iflag & ISTRIP)
/*
* For POSIX compliance, \377 must
* pass thru unmolested
* for the case of PARMRK set.
*/
if ((!(tp->t_modes.c_iflag &
PARMRK)) || (c != 0377) ||
(readp >= bp->b_wptr) ||
(*readp != 0)) {
c &= 0177;
}
/*
* First, check that this hasn't been escaped
* with the "literal next" character.
*/
if (tp->t_state & TS_PLNCH) {
tp->t_state &= ~TS_PLNCH;
tp->t_modes.c_lflag &= ~FLUSHO;
*writep++ = c;
continue;
}
/*
* Setting a special character to VDISABLE
* disables it, so if this character is
* VDISABLE, it should not be compared with
* any of the special characters.
* It should, however, restart frozen output if
* IXON and IXANY are set.
*/
if (c == VDISABLE) {
if (tp->t_modes.c_iflag & IXON &&
tp->t_state & TS_TTSTOP &&
tp->t_modes.c_iflag & IXANY) {
tp->t_state &= ~TS_TTSTOP;
(void) putctl(wrq->q_next,
M_START);
}
tp->t_modes.c_lflag &= ~FLUSHO;
*writep++ = c;
continue;
}
/*
* If stopped, start if you can; if running,
* stop if you must.
*/
if (tp->t_modes.c_iflag & IXON) {
if (tp->t_state & TS_TTSTOP) {
if (c == tp->t_modes.c_cc[VSTART] ||
tp->t_modes.c_iflag & IXANY) {
tp->t_state &= ~TS_TTSTOP;
(void) putctl(wrq->q_next, M_START);
}
} else {
if (c == tp->t_modes.c_cc[VSTOP]) {
tp->t_state |= TS_TTSTOP;
(void) putctl(wrq->q_next, M_STOP);
}
}
if (c == tp->t_modes.c_cc[VSTOP] ||
c == tp->t_modes.c_cc[VSTART])
continue;
}
/*
* Check for "literal next" character and
* "flush output" character. Note that we
* omit checks for ISIG and ICANON, since
* the IEXTEN setting subsumes them.
*/
if (tp->t_modes.c_lflag & IEXTEN) {
if (c == tp->t_modes.c_cc[VLNEXT]) {
/*
* Remember that we saw a
* "literal next" while
* scanning input, but leave
* it in the message so that
* the service routine can see
* it too.
*/
tp->t_state |= TS_PLNCH;
tp->t_modes.c_lflag &= ~FLUSHO;
*writep++ = c;
continue;
}
if (c == tp->t_modes.c_cc[VDISCARD]) {
do_flush_output(c, wrq, tp);
continue;
}
}
tp->t_modes.c_lflag &= ~FLUSHO;
/*
* Check for signal-generating characters.
*/
if (tp->t_modes.c_lflag & ISIG) {
if (c == tp->t_modes.c_cc[VINTR]) {
do_signal(q, SIGINT, c, 1, 0);
continue;
}
if (c == tp->t_modes.c_cc[VQUIT]) {
do_signal(q, SIGQUIT, c, 1, 0);
continue;
}
if (c == tp->t_modes.c_cc[VSUSP]) {
do_signal(q, SIGTSTP, c, 1, 0);
continue;
}
if (c == tp->t_modes.c_cc[VSWTCH]) {
/*
* Shouldn't ever see this;
* ignore it.
*/
continue;
}
}
/*
* Throw away CR if IGNCR set, or turn
* it into NL if ICRNL set.
*/
if (c == '\r') {
if (tp->t_modes.c_iflag & IGNCR)
continue;
if (tp->t_modes.c_iflag & ICRNL)
c = '\n';
} else {
/*
* Turn NL into CR if INLCR set.
*/
if (c == '\n' &&
tp->t_modes.c_iflag & INLCR)
c = '\r';
}
/*
* Map upper case input to lower case if
* IUCLC flag set.
*/
if ((tp->t_modes.c_iflag & IUCLC) &&
c >= 'A' && c <= 'Z')
c += 'a' - 'A';
/*
* Put the possibly-transformed character
* back in the message.
*/
*writep++ = c;
}
/*
* If we didn't copy some characters because
* we were ignoring them, fix the size of the
* data block by adjusting the write pointer.
* XXX This may result in a zero-length block;
* will this cause anybody gastric distress?
*/
bp->b_wptr -= (readp - writep);
} else {
/*
* We won't be doing anything other than possibly
* stripping the input.
*/
if (tp->t_modes.c_iflag & ISTRIP) {
while (readp < bp->b_wptr) {
/*
* For POSIX compliance, \377 must
* pass thru unmolested
* for the case of PARMRK set.
*/
if (!((!(tp->t_modes.c_iflag &
PARMRK)) || (*readp != 0377) ||
(readp >= bp->b_wptr) ||
(*(readp + 1) != 0)))
*writep++ = *readp++;
else
*writep++ = *readp++ & 0177;
}
}
tp->t_modes.c_lflag &= ~FLUSHO;
}
/*
* Flow control: send "stop input" message if our queue is
* approaching its high-water mark.
*
* Set QWANTW to ensure that the read queue service
* procedure gets run when nextq empties up again, so that
* it can unstop the input.
*/
if ((tp->t_modes.c_iflag & IXOFF) &&
!(tp->t_state & TS_TBLOCK) &&
q->q_count >= TTXOHI) {
nextq->q_flag |= QWANTW;
tp->t_state |= TS_TBLOCK;
(void) putctl(wrq->q_next, M_STOPI);
}
} while ((bp = bp->b_cont) != NULL); /* next block, if any */
/*
* Queue the message for service procedure.
*/
putq(q, mp);
}
/*
* Line discipline input server processing. Erase/kill and escape ('\')
* processing, gathering into messages, upper/lower case input mapping.
*/
static int
ldtermrsrv(q)
register queue_t *q;
{
register ldterm_state_t *tp;
mblk_t *mp;
register mblk_t *bp;
register mblk_t *bpt;
register unsigned char c;
int ebsize;
tp = (ldterm_state_t *)q->q_ptr;
if (tp->t_state & TS_RESCAN) {
/*
* Canonicalization was turned on or off.
* Put the message being assembled back in the input queue,
* so that we rescan it.
*/
if (tp->t_message != NULL) {
putbq(q, tp->t_message);
tp->t_message = NULL;
tp->t_endmsg = NULL;
tp->t_msglen = 0;
}
tp->t_state &= ~TS_RESCAN;
}
bpt = NULL;
while ((mp = getq(q)) != NULL) {
if (mp->b_datap->db_type <= QPCTL && !canput(q->q_next)) {
putbq(q, mp);
goto out; /* read side is blocked */
}
switch (mp->b_datap->db_type) {
default:
putnext(q, mp); /* pass it on */
continue;
case M_FLUSH:
/*
* Flush everything we haven't looked at yet.
*/
flushq(q, FLUSHDATA);
/*
* Flush everything we have looked at.
*/
freemsg(tp->t_message);
tp->t_message = NULL;
tp->t_endmsg = NULL;
tp->t_msglen = 0;
tp->t_rocount = 0; /* if it hasn't been typed, */
tp->t_rocol = 0; /* it hasn't been echoed :-) */
putnext(q, mp); /* pass it on */
continue;
case M_HANGUP:
/*
* Flush everything we haven't looked at yet.
*/
flushq(q, FLUSHDATA);
/*
* Flush everything we have looked at.
*/
freemsg(tp->t_message);
tp->t_message = NULL;
tp->t_endmsg = NULL;
tp->t_msglen = 0;
tp->t_rocount = 0; /* if it hasn't been typed, */
tp->t_rocol = 0; /* it hasn't been echoed :-) */
/*
* Restart output, since it's probably got nowhere to
* go anyway, and we're probably not going to see
* another ^Q for a while.
*/
tp->t_state &= ~TS_TTSTOP;
(void) putctl(WR(q)->q_next, M_START);
/*
* This guy will travel up the read queue, flushing
* as it goes, get turned around at the stream head,
* and travel back down the write queue, flushing as
* it goes.
*/
(void) putctl1(q->q_next, M_FLUSH, FLUSHRW);
/*
* This guy will travel down the write queue, flushing
* as it goes, get turned around at the driver,
* and travel back up the read queue, flushing as
* it goes.
*/
(void) putctl1(WR(q), M_FLUSH, FLUSHRW);
/*
* Now that that's done, we send a SIGCONT upstream,
* followed by the M_HANGUP.
*/
(void) putctl1(q->q_next, M_PCSIG, SIGCONT);
(void) putnext(q, mp);
continue;
case M_IOCACK:
/*
* Augment whatever information the driver is
* returning with the information we supply.
*/
ioctl_reply(q, mp);
continue;
case M_CTL:
switch (*mp->b_rptr) {
case MC_NOCANON:
/*
* Note: this is very nasty. It's not
* clear what the right thing to do
* with a partial message is; we throw
* it out.
*/
if (tp->t_message != NULL) {
freemsg(tp->t_message);
tp->t_message = NULL;
tp->t_endmsg = NULL;
tp->t_msglen = 0;
tp->t_rocount = 0;
tp->t_rocol = 0;
}
tp->t_state |= TS_NOCANSRV;
break;
case MC_DOCANON:
tp->t_state &= ~TS_NOCANSRV;
break;
}
putnext(q, mp);
continue;
case M_DATA:
break;
}
/*
* This is an M_DATA message.
*/
/*
* If somebody below us ("intelligent" communications board,
* pseudo-tty controlled by an editor) is doing
* canonicalization, don't scan it for special characters.
*/
if (tp->t_state & TS_NOCANSRV) {
putnext(q, mp);
continue;
}
if (tp->t_modes.c_lflag & ICANON) {
bp = mp;
ebsize = bp->b_wptr - bp->b_rptr;
if (ebsize > EBSIZE)
ebsize = EBSIZE;
if ((bpt = newmsg(tp)) != NULL) {
mblk_t *bcont;
do {
bcont = bp->b_cont;
while (bp->b_rptr < bp->b_wptr) {
c = *bp->b_rptr++;
if ((bpt =
do_canon_input(c, bpt,
ebsize, q, tp)) == NULL)
break;
}
/*
* Release this block.
*/
freeb(bp);
if (bpt == NULL) {
printf("ldtermrsrv: out of blocks\n");
freemsg(bcont);
break;
}
} while ((bp = bcont) != NULL);
}
} else
do_noncanon_input(mp, q, tp);
/*
* Send whatever we echoed downstream.
*/
if (tp->t_echomp != NULL) {
putnext(WR(q), tp->t_echomp);
tp->t_echomp = NULL;
}
}
out:
/*
* Flow control: send start message if blocked and
* our queue is below its low water mark.
*/
if ((tp->t_modes.c_iflag & IXOFF) && (tp->t_state & TS_TBLOCK) &&
q->q_count <= TTXOLO) {
tp->t_state &= ~TS_TBLOCK;
(void) putctl(WR(q), M_STARTI);
}
}
/*
* Do canonical mode input; check whether this character is to be treated as a
* special character - if so, check whether it's equal to any of the special
* characters and handle it accordingly. Otherwise, just add it to the current
* line.
*/
static mblk_t *
do_canon_input(c, bpt, ebsize, q, tp)
register unsigned char c;
register mblk_t *bpt;
int ebsize;
queue_t *q;
register ldterm_state_t *tp;
{
register queue_t *wrq = WR(q);
int i;
/*
* If the previous character was the "literal next" character, treat
* this character as regular input.
*/
if (tp->t_state & TS_SLNCH)
goto escaped;
/*
* Setting a special character to VDISABLE disables it, so if this
* character is VDISABLE, it should not be compared with any of the
* special characters.
*/
if (c == VDISABLE) {
tp->t_state &= ~TS_QUOT;
goto escaped;
}
/*
* If this character is the literal next character, echo it as '^',
* backspace over it, and record that fact.
*/
if ((tp->t_modes.c_lflag & IEXTEN) && c == tp->t_modes.c_cc[VLNEXT]) {
if (tp->t_modes.c_lflag & ECHO)
output_echo_string((unsigned char *)"^\b", 2, wrq,
ebsize, tp);
tp->t_state &= ~TS_QUOT; /* cannot be escaped with \ */
tp->t_state |= TS_SLNCH;
goto out;
}
/*
* Check for the editing characters.
*/
if (c == tp->t_modes.c_cc[VERASE]) {
if (tp->t_state & TS_QUOT) {
/*
* Get rid of the backslash, and put the erase
* character in its place.
*/
curline_erase(wrq, ebsize, tp);
bpt = tp->t_endmsg;
goto escaped;
} else {
/*
* Erase the most-recently-typed character.
*/
curline_erase(wrq, ebsize, tp);
bpt = tp->t_endmsg;
goto out;
}
}
if ((tp->t_modes.c_lflag & IEXTEN) && c == tp->t_modes.c_cc[VWERASE]) {
tp->t_state &= ~TS_QUOT; /* cannot be escaped with \ */
curline_werase(wrq, ebsize, tp);
bpt = tp->t_endmsg;
goto out;
}
if (c == tp->t_modes.c_cc[VKILL]) {
if (tp->t_state & TS_QUOT) {
/*
* Get rid of the backslash, and put the kill character
* in its place.
*/
curline_erase(wrq, ebsize, tp);
bpt = tp->t_endmsg;
goto escaped;
} else {
/*
* Erase the entire line.
*/
curline_kill(wrq, ebsize, tp);
bpt = tp->t_endmsg;
goto out;
}
}
if ((tp->t_modes.c_lflag & IEXTEN) && c == tp->t_modes.c_cc[VREPRINT]) {
curline_reprint(wrq, ebsize, tp);
goto out;
}
/*
* If the preceding character was a backslash:
* if the current character is an EOF, get rid of the backslash
* and treat the EOF as data;
* if we're in XCASE mode and the current character is part
* of a backslash-X escape sequence, process it;
* otherwise, just treat the current character normally.
*/
if (tp->t_state & TS_QUOT) {
tp->t_state &= ~TS_QUOT;
if (c == tp->t_modes.c_cc[VEOF]) {
/*
* EOF character.
* Since it's escaped, get rid of the backslash and put
* the EOF character in its place.
*/
curline_erase(wrq, ebsize, tp);
bpt = tp->t_endmsg;
} else {
/*
* If we're in XCASE mode, and the current character
* is part of a backslash-X sequence, get rid of the
* backslash and replace the current character with
* what that sequence maps to.
*/
if ((tp->t_modes.c_lflag & XCASE) &&
imaptab[c] != '\0') {
curline_erase(wrq, ebsize, tp);
bpt = tp->t_endmsg;
c = imaptab[c];
}
}
} else {
/*
* Previous character wasn't backslash; check whether this
* was the EOF character.
*/
if (c == tp->t_modes.c_cc[VEOF]) {
/*
* EOF character.
* Don't echo it unless ECHOCTL is set, don't stuff it
* in the current line, but send the line up the
* stream.
*/
if ((tp->t_modes.c_lflag & ECHOCTL) &&
(tp->t_modes.c_lflag & ECHO)) {
i = echo_char(c, wrq, ebsize, tp);
while (i > 0) {
output_echo_char('\b', wrq, ebsize,
tp);
i--;
}
}
bpt->b_datap->db_type = M_DATA;
msg_upstream(q, tp);
bpt = newmsg(tp);
goto out;
}
}
escaped:
if (tp->t_msglen == TTYHOG) {
/*
* Character will cause line to overflow.
* Ring the bell or discard all input, and don't save the
* character away.
*/
if (tp->t_modes.c_iflag & IMAXBEL)
output_echo_char(_CTRL(g), wrq, ebsize, tp);
else
(void) putctl1(q, M_FLUSH, FLUSHR);
tp->t_state &= ~TS_SLNCH;
goto out;
}
/*
* Add the character to the current line.
*/
if (bpt->b_wptr >= bpt->b_datap->db_lim) {
/*
* No more room in this mblk; save this one away, and
* allocate a new one.
*/
bpt->b_datap->db_type = M_DATA;
if ((bpt = allocb(IBSIZE, BPRI_MED)) == NULL)
goto out;
/*
* Chain the new one to the end of the old one, and
* mark it as the last block in the current line.
*/
tp->t_endmsg->b_cont = bpt;
tp->t_endmsg = bpt;
}
*bpt->b_wptr++ = c;
tp->t_msglen++;
if (!(tp->t_state & TS_SLNCH) &&
(c == '\n' || (c != VDISABLE && (c == tp->t_modes.c_cc[VEOL] ||
((tp->t_modes.c_lflag & IEXTEN) &&
c == tp->t_modes.c_cc[VEOL2]))))) {
/*
* It's a line-termination character; send the line
* up the stream.
*/
bpt->b_datap->db_type = M_DATA;
msg_upstream(q, tp);
if ((bpt = newmsg(tp)) == NULL)
goto out;
} else {
/*
* Character was escaped with LNEXT.
*/
if (tp->t_rocount++ == 0)
tp->t_rocol = tp->t_col;
tp->t_state &= ~(TS_SLNCH|TS_QUOT);
if (c == '\\')
tp->t_state |= TS_QUOT;
}
/*
* Echo it.
*/
if (tp->t_state & TS_ERASE) {
tp->t_state &= ~TS_ERASE;
if (tp->t_modes.c_lflag & ECHO)
output_echo_char('/', wrq, ebsize, tp);
}
if (tp->t_modes.c_lflag & ECHO)
(void) echo_char(c, wrq, ebsize, tp);
else {
/*
* Echo NL when ECHO turned off, if ECHONL flag is set.
*/
if (c == '\n' && (tp->t_modes.c_lflag & ECHONL))
output_echo_char(c, wrq, ebsize, tp);
}
out:
return (bpt);
}
/*
* Get the character at the end of the current line, and remove it from the
* current line. Return -1 if the current line is empty.
*/
static int
curline_unget(tp)
register ldterm_state_t *tp;
{
register mblk_t *bpt;
if ((bpt = tp->t_endmsg) == NULL)
return (-1); /* no buffers */
if (bpt->b_rptr == bpt->b_wptr)
return (-1); /* zero-length record */
tp->t_msglen--; /* one fewer character */
return (*--bpt->b_wptr);
}
/*
* Trim any zero-length mblks from the end of the current line (they may have
* been left there by erasing characters from the line).
*/
static void
curline_trim(tp)
register ldterm_state_t *tp;
{
register mblk_t *bpt;
register mblk_t *bp;
if ((bpt = tp->t_endmsg) == NULL)
panic("curline_trim called with no message");
if (bpt->b_rptr == bpt->b_wptr) {
/*
* This mblk is now empty.
* Find the previous mblk; throw this one away, unless
* it's the first one.
*/
bp = tp->t_message;
if (bp != bpt) {
while (bp->b_cont != bpt) {
if ((bp = bp->b_cont) == NULL)
panic("curline_trim: current input line mislinked");
}
bp->b_cont = NULL;
freeb(bpt);
tp->t_endmsg = bp; /* point to that mblk */
}
}
}
/*
* Rubout one character from the current line being built for tp
* as cleanly as possible. q is the write queue for tp.
*/
static void
curline_rubout(c, q, ebsize, tp)
register unsigned char c;
register queue_t *q;
int ebsize;
register ldterm_state_t *tp;
{
register int tabcols;
static unsigned char crtrubout[] = "\b \b\b \b";
#define RUBOUT1 &crtrubout[3] /* rub out one position */
#define RUBOUT2 &crtrubout[0] /* rub out two positions */
if (!(tp->t_modes.c_lflag & ECHO))
return;
if (tp->t_modes.c_lflag & ECHOE) {
/*
* "CRT rubout"; try erasing it from the screen.
*/
if (tp->t_rocount == 0) {
/*
* After the character being erased was echoed,
* some data was written to the terminal; we
* can't erase it cleanly, so we just reprint the
* whole line as if the user had typed the
* reprint character.
*/
curline_reprint(q, ebsize, tp);
return;
} else {
/*
* XXX what about escaped characters?
*/
switch (typetab[c]) {
case PRINTABLE:
if ((tp->t_modes.c_lflag & XCASE) &&
omaptab[c])
output_echo_string(RUBOUT1, 3, q,
ebsize, tp);
output_echo_string(RUBOUT1, 3, q, ebsize, tp);
break;
case VTAB:
case BACKSPACE:
case CONTROL:
case RETURN:
case NEWLINE:
if (tp->t_modes.c_lflag & ECHOCTL)
output_echo_string(RUBOUT2, 6, q,
ebsize, tp);
break;
case TAB:
if (tp->t_rocount < tp->t_msglen) {
/*
* While the tab being erased was
* expanded, some data was written
* to the terminal; we can't erase it
* cleanly, so we just reprint the
* whole line as if the user had typed
* the reprint character.
*/
curline_reprint(q, ebsize, tp);
return;
}
tabcols = curline_tabcols(tp);
while (--tabcols >= 0)
output_echo_char('\b', q, ebsize, tp);
break;
}
}
} else if (tp->t_modes.c_lflag & ECHOPRT) {
/*
* "Printing rubout"; echo it between \ and /.
*/
if (!(tp->t_state & TS_ERASE)) {
output_echo_char('\\', q, ebsize, tp);
tp->t_state |= TS_ERASE;
}
(void) echo_char(c, q, ebsize, tp);
} else
(void) echo_char(tp->t_modes.c_cc[VERASE], q, ebsize, tp);
tp->t_rocount--; /* we "unechoed" this character */
}
/*
* Find the number of characters the tab we just deleted took up by
* zipping through the current line and recomputing the column number.
*/
static int
curline_tabcols(tp)
register ldterm_state_t *tp;
{
register int col;
register mblk_t *bp;
register unsigned char *readp;
register unsigned char c;
col = tp->t_rocol;
bp = tp->t_message;
do {
readp = bp->b_rptr;
while (readp < bp->b_wptr) {
c = *readp++;
if (tp->t_modes.c_lflag & ECHOCTL) {
if (c <= 037 && c != '\t' && c != '\n' ||
c == 0177) {
/*
* One column for '^' and one for
* (c + ' ').
*/
col += 2;
continue;
}
}
/*
* Column position calculated here.
*/
switch (typetab[c]) {
/* Ordinary characters; advance by one. */
case PRINTABLE:
col++;
break;
/* Non-printing characters; nothing happens. */
case CONTROL:
break;
/* Backspace */
case BACKSPACE:
if (col != 0)
col--;
break;
/* Newline; column depends on flags. */
case NEWLINE:
if (tp->t_modes.c_oflag & ONLRET)
col = 0;
break;
/* tab */
case TAB:
col |= 07;
col++;
break;
/* vertical motion */
case VTAB:
break;
/* carriage return */
case RETURN:
col = 0;
break;
}
}
} while ((bp = bp->b_cont) != NULL); /* next block, if any */
/*
* "col" is now the column number before the tab.
* "tp->t_col" is still the column number after the tab,
* since we haven't erased the tab yet.
* Thus "tp->t_col - col" is the number of positions the tab
* moved.
*/
col = tp->t_col - col;
if (col > 8)
col = 8; /* overflow screw */
return (col);
}
/*
* Erase a single character from the current line; remove the character and
* properly echo the erase character.
*/
static void
curline_erase(q, ebsize, tp)
register queue_t *q;
int ebsize;
register ldterm_state_t *tp;
{
register int c;
if ((c = curline_unget(tp)) != -1) {
curline_rubout((unsigned char)c, q, ebsize, tp);
curline_trim(tp);
}
}
/*
* Erase an entire word from the current line; remove the characters and, for
* each one, echo an erase character.
*/
static void
curline_werase(q, ebsize, tp)
register queue_t *q;
int ebsize;
register ldterm_state_t *tp;
{
register int c;
/*
* Erase trailing white space, if any.
*/
while ((c = curline_unget(tp)) == ' ' || c == '\t') {
curline_rubout((unsigned char)c, q, ebsize, tp);
curline_trim(tp);
}
/*
* Erase non-white-space characters, if any.
*/
while (c != -1 && c != ' ' && c != '\t') {
curline_rubout((unsigned char)c, q, ebsize, tp);
curline_trim(tp);
c = curline_unget(tp);
}
if (c != -1) {
/*
* We removed one too many characters; put the last one
* back.
*/
tp->t_endmsg->b_wptr++; /* put 'c' back */
tp->t_msglen++;
}
}
/*
* Kill the entire current line; remove the characters and, if ECHOKE is set,
* echo an erase character for each one, otherwise echo the kill character and,
* if ECHOK is set, a newline.
*/
static void
curline_kill(q, ebsize, tp)
register queue_t *q;
int ebsize;
register ldterm_state_t *tp;
{
register int c;
if ((tp->t_modes.c_lflag & ECHOKE) && tp->t_msglen == tp->t_rocount) {
while ((c = curline_unget(tp)) != -1) {
curline_rubout((unsigned char)c, q, ebsize, tp);
curline_trim(tp);
}
} else {
(void) echo_char(tp->t_modes.c_cc[VKILL], q, ebsize, tp);
if (tp->t_modes.c_lflag & ECHOK)
(void) echo_char('\n', q, ebsize, tp);
while (curline_unget(tp) != -1)
curline_trim(tp);
tp->t_rocount = 0;
}
tp->t_state &= ~(TS_QUOT|TS_ERASE|TS_SLNCH);
}
/*
* Reprint the current input line. First, echo the reprint character, if it
* hasn't been disabled.
* XXX just the current line, not the whole queue?
* What about DEFECHO mode?
*/
static void
curline_reprint(q, ebsize, tp)
register queue_t *q;
int ebsize;
register ldterm_state_t *tp;
{
register mblk_t *bp;
register unsigned char *readp;
if (tp->t_modes.c_cc[VREPRINT] != (unsigned char)0)
(void) echo_char(tp->t_modes.c_cc[VREPRINT], q, ebsize, tp);
output_echo_char('\n', q, ebsize, tp);
bp = tp->t_message;
do {
readp = bp->b_rptr;
while (readp < bp->b_wptr)
(void) echo_char(*readp++, q, ebsize, tp);
} while ((bp = bp->b_cont) != NULL); /* next block, if any */
tp->t_state &= ~TS_ERASE;
tp->t_rocount = tp->t_msglen; /* we reechoed the entire line */
tp->t_rocol = 0;
}
/*
* Do non-canonical mode input.
*/
static void
do_noncanon_input(mp, q, tp)
register mblk_t *mp;
queue_t *q;
register ldterm_state_t *tp;
{
queue_t *wrq = WR(q);
int ebsize;
register mblk_t *bp, *prevbp;
mblk_t *savebp;
register unsigned char *rptr;
for (bp = mp, prevbp = NULL; bp != NULL;
prevbp = bp, bp = bp->b_cont) {
while (bp->b_rptr == bp->b_wptr) {
/*
* Zero-length block. Throw it away.
*/
if (prevbp == NULL)
mp = bp->b_cont;
else
prevbp->b_cont = bp->b_cont;
savebp = bp;
bp = bp->b_cont;
savebp->b_cont = NULL;
freeb(savebp);
if (bp == NULL)
return; /* entire message gone */
}
if (tp->t_modes.c_lflag & (ECHO|ECHONL)) {
/*
* Echo the data in this message.
*/
if (tp->t_modes.c_lflag & ECHO) {
ebsize = bp->b_wptr - bp->b_rptr;
if (ebsize > EBSIZE)
ebsize = EBSIZE;
rptr = bp->b_rptr;
while (rptr < bp->b_wptr)
(void) echo_char(*rptr++, wrq,
ebsize, tp);
} else {
/*
* Echo NL, even though ECHO is not
* set.
*/
rptr = bp->b_rptr;
while (rptr < bp->b_wptr) {
if (*rptr++ == '\n')
output_echo_char('\n',
wrq, 1, tp);
}
}
}
}
putnext(q, mp);
/*
* Send whatever we echoed downstream.
*/
if (tp->t_echomp != NULL) {
putnext(wrq, tp->t_echomp);
tp->t_echomp = NULL;
}
}
/*
* Echo a typed character to the terminal.
* Returns the number of characters printed.
*/
static int
echo_char(c, q, ebsize, tp)
register unsigned char c;
register queue_t *q;
int ebsize;
register ldterm_state_t *tp;
{
register int i;
if (!(tp->t_modes.c_lflag & ECHO))
return (0);
i = 0;
if (tp->t_modes.c_lflag & ECHOCTL) {
if (c <= 037 && c != '\t' && c != '\n') {
output_echo_char('^', q, ebsize, tp);
i++;
if (tp->t_modes.c_oflag & OLCUC)
c += 'a' - 1;
else
c += 'A' - 1;
} else if (c == 0177) {
output_echo_char('^', q, ebsize, tp);
i++;
c = '?';
}
}
output_echo_char(c, q, ebsize, tp);
return (i + 1);
}
/*
* Put a character generated as part of an echo operation on the output queue.
*/
static void
output_echo_char(c, q, bsize, tp)
register unsigned char c;
register queue_t *q;
int bsize;
register ldterm_state_t *tp;
{
register mblk_t *curbp;
/*
* Don't even look at the characters unless we
* have something useful to do with them.
*/
if ((tp->t_modes.c_oflag & OPOST) ||
((tp->t_modes.c_lflag & XCASE) &&
(tp->t_modes.c_lflag & ICANON))) {
register mblk_t *mp;
if ((mp = allocb(4, BPRI_HI)) == NULL) {
printf("output_echo_char: out of blocks\n");
return;
}
*mp->b_wptr++ = c;
mp = do_output(q, mp, &tp->t_echomp, tp, bsize, 1);
if (mp != NULL)
freemsg(mp);
} else {
/*
* No fancy processing required; just glue the character onto
* the end of the to-be-echoed message, allocating or
* extending it if necessary.
*/
if ((curbp = tp->t_echomp) != NULL) {
while (curbp->b_cont != NULL)
curbp = curbp->b_cont;
if (curbp->b_datap->db_lim == curbp->b_wptr) {
register mblk_t *newbp;
if ((newbp = allocb(bsize, BPRI_HI)) == NULL) {
printf("output_echo_char: out of blocks\n");
return;
}
curbp->b_cont = newbp;
curbp = newbp;
}
} else {
if ((curbp = allocb(bsize, BPRI_HI)) == NULL) {
printf("output_echo_char: out of blocks\n");
return;
}
tp->t_echomp = curbp;
}
*curbp->b_wptr++ = c;
}
}
/*
* Copy a string, of length len, of characters generated as part of an echo
* operation to the output queue.
*/
static void
output_echo_string(cp, len, q, bsize, tp)
register unsigned char *cp;
register int len;
register queue_t *q;
int bsize;
register ldterm_state_t *tp;
{
while (len > 0) {
output_echo_char(*cp++, q, bsize, tp);
len--;
}
}
static mblk_t *
newmsg(tp)
register ldterm_state_t *tp;
{
register mblk_t *bp;
/*
* If no current message, allocate a block
* for it.
*/
if ((bp = tp->t_endmsg) == NULL) {
if ((bp = allocb(IBSIZE, BPRI_MED)) == NULL) {
printf("newmsg: out of blocks\n");
return (bp);
}
tp->t_message = bp;
tp->t_endmsg = bp;
}
return (bp);
}
static void
msg_upstream(q, tp)
register queue_t *q;
register ldterm_state_t *tp;
{
putnext(q, tp->t_message);
tp->t_message = NULL;
tp->t_endmsg = NULL;
tp->t_msglen = 0;
tp->t_rocount = 0;
}
/*
* Re-enable the write-side service procedure. When an allocation failure
* causes write-side processing to stall, we disable the write side and
* arrange to call this function when allocation once again becomes possible.
*/
static int
ldterm_wenable(addr)
long addr;
{
register queue_t *q = (queue_t *)addr;
register ldterm_state_t *tp;
if ((tp = (ldterm_state_t *)q->q_ptr) == NULL)
return;
/*
* The bufcall is no longer pending.
*/
tp->t_wbufcid = 0;
enableok(q);
qenable(q);
}
/*
* Line discipline output queue put procedure. Attempts to process the
* message directly and send it on downstream, queueing it only if there's
* already something pending or if its downstream neighbor is clogged.
*/
static int
ldtermwput(q, mp)
register queue_t *q;
register mblk_t *mp;
{
unsigned char type = mp->b_datap->db_type;
/*
* Always process priority messages, regardless of whether or not our
* queue is nonempty.
*/
if (type >= QPCTL) {
switch (type) {
case M_FLUSH:
/*
* This is coming from above, so we only handle the
* write queue here. If FLUSHR is set, it will get
* turned around at the driver, and the read procedure
* will see it eventually.
*/
if (*mp->b_rptr & FLUSHW)
flushq(q, FLUSHDATA);
putnext(q, mp);
break;
default:
/* Pass it through unmolested. */
putnext(q, mp);
break;
}
return;
}
/*
* If our queue is nonempty or there's a traffic jam downstream, this
* message must get in line.
*/
if (q->q_first != NULL || !canput(q->q_next)) {
/*
* Exception: ioctls, except for those defined to take effect
* after output has drained, should be processed immediately.
*/
if (type == M_IOCTL) {
register struct iocblk *iocp;
iocp = (struct iocblk *)mp->b_rptr;
switch (iocp->ioc_cmd) {
/*
* Queue these.
*/
case TCSETSW:
case TCSETSF:
case TCSETAW:
case TCSETAF:
case TCSBRK:
break;
/*
* Handle all others immediately.
*/
default:
ldterm_wputmsgs++;
(void) ldtermwmsg(q, mp);
return;
}
}
putq(q, mp);
return;
}
/*
* We can take the fast path through, by simply calling ldtermwmsg to
* dispose of mp.
*/
ldterm_wputmsgs++;
(void) ldtermwmsg(q, mp);
}
/*
* Line discipline output queue service procedure.
*/
static int
ldtermwsrv(q)
register queue_t *q;
{
register mblk_t *mp;
/*
* We expect this loop to iterate at most once, but must be prepared
* for more in case our upstream neighbor isn't paying strict
* attention to what canput tells it.
*/
while ((mp = getq(q)) != NULL) {
ldterm_wsrvmsgs++;
/*
* N.B.: ldtermwput has already handled high-priority
* messages, so we don't have to worry about them here.
* Hence, the putbq call is safe.
*/
if (!canput(q->q_next)) {
putbq(q, mp);
break;
}
if (!ldtermwmsg(q, mp)) {
/*
* Couldn't handle the whole thing; give up for now
* and wait to be rescheduled.
*/
break;
}
}
}
/*
* Process the write-side message denoted by mp. If mp can't be processed
* completely (due to allocation failures), put the residual unprocessed part
* on the front of the write queue, disable the queue, and schedule a bufcall
* to arrange to complete its processing later.
*
* Return 1 if the message was processed completely and 0 if not.
*
* This routine is called from both ldtermwput and ldtermwsrv to do the actual
* work of dealing with mp. ldtermwput will have already dealt with high
* priority messages.
*/
static int
ldtermwmsg(q, mp)
register queue_t *q;
register mblk_t *mp;
{
register ldterm_state_t *tp;
register mblk_t *residmp = NULL;
register u_int size;
switch (mp->b_datap->db_type) {
case M_IOCTL:
do_ioctl(q, mp);
break;
case M_DATA: {
mblk_t *omp = NULL;
tp = (ldterm_state_t *)q->q_ptr;
tp->t_rocount = 0;
/*
* Don't even look at the characters unless we
* have something useful to do with them.
*/
if ((tp->t_modes.c_oflag & OPOST) ||
((tp->t_modes.c_lflag & XCASE) &&
(tp->t_modes.c_lflag & ICANON))) {
residmp = do_output(q, mp, &omp, tp, OBSIZE, 0);
if ((mp = omp) == NULL)
break;
}
if (!(tp->t_modes.c_lflag & FLUSHO)) {
tk_nout += msgdsize(mp);
putnext(q, mp);
} else
freemsg(mp); /* drop on floor */
break;
}
default:
putnext(q, mp); /* pass it through unmolested */
break;
}
if (residmp == NULL)
return (1);
/*
* An allocation failure occurred that prevented the message from
* being completely processed. First, disable our queue, since it's
* pointless to attempt further processing until the allocation
* situation is resolved. (This must precede the putbq call below,
* which would otherwise mark the queue to be serviced.)
*/
noenable(q);
/*
* Stuff the remnant on our write queue so that we can complete it
* later when times become less lean. Note that this sets QFULL, so
* that our upstream neighbor will be blocked by flow control.
*/
putbq(q, residmp);
/*
* Schedule a bufcall to re-enable the queue. The failure won't have
* been for an allocation of more than OBSIZE bytes, so don't ask for
* more than that from bufcall.
*/
size = msgdsize(residmp);
if (size > OBSIZE)
size = OBSIZE;
if (tp->t_wbufcid)
unbufcall(tp->t_wbufcid);
tp->t_wbufcid = bufcall(size, BPRI_MED, ldterm_wenable, (long)q);
return (0);
}
/*
* Perform output processing on the message denoted by imp, adding the output
* characters to the message denoted by *omp (allocating it if necessary).
* Return the unprocessed residual input, freeing those blocks of the input
* that were completely processed.
*
* Normally, the return value will be NULL. If an allocation failure prevents
* the routine from having enough space to continue accumulating output, it
* stops where it is and hands back what it's done, leaving the caller to
* decide whether to free the remaining input or to arrange for a bufcall to
* try again later.
*/
static mblk_t *
do_output(q, imp, omp, tp, bsize, echoing)
register queue_t *q;
mblk_t *imp; /* head of input message we're examining */
mblk_t **omp; /* addr of head of output we're constructing */
register ldterm_state_t *tp;
int bsize;
int echoing;
{
register mblk_t *ibp; /* block we're examining from input message */
register mblk_t *obp; /* block we're filling in output message */
mblk_t *oobp; /* old value of obp; valid if NEW_BLOCK fails */
mblk_t **contpp; /* where to stuff ptr to newly-allocated blk */
register unsigned char c;
register int count, ctype;
register int chars_left;
/*
* Allocate a new block into which to put characters. If we can't,
* set oobp to the previous value of obp and indicate failure.
*/
#define NEW_BLOCK() \
(oobp = obp, !(obp = allocb(bsize, BPRI_MED)) ? 0 : \
(*contpp = obp, contpp = &obp->b_cont, \
chars_left = obp->b_datap->db_lim - obp->b_wptr, 1))
ibp = imp;
/*
* When we allocate the first block of a message, we should stuff
* the pointer to it in "*omp". All subsequent blocks should
* have the pointer to them stuffed into the "b_cont" field of the
* previous block. "contpp" points to the place where we should
* stuff the pointer.
*
* If we already have a message we're filling in, continue doing
* so.
*/
if ((obp = *omp) != NULL) {
/* Find end of previously accumulated output. */
for (; obp->b_cont != NULL; obp = obp->b_cont)
;
contpp = &obp->b_cont;
chars_left = obp->b_datap->db_lim - obp->b_wptr;
} else {
contpp = omp;
chars_left = 0;
}
for (;;) {
mblk_t *cibp = ibp->b_cont;
while (ibp->b_rptr < ibp->b_wptr) {
/*
* Make sure there's room for one more
* character.
*/
if (chars_left == 0 && !NEW_BLOCK())
goto outofbufs;
/*
* If doing XCASE processing (not very likely,
* in this day and age), look at each character
* individually.
*/
if ((tp->t_modes.c_lflag & XCASE) &&
(tp->t_modes.c_lflag & ICANON)) {
c = *ibp->b_rptr++;
/*
* If character is mapped on output, put out
* a backslash followed by what it is
* mapped to.
*/
if (omaptab[c] != 0 &&
(!echoing || c != '\\')) {
/*
* Backslash is an ordinary character.
*/
tp->t_col++;
*obp->b_wptr++ = '\\';
chars_left--;
if (chars_left == 0 && !NEW_BLOCK()) {
/*
* Make state consistent.
*/
ibp->b_rptr--;
tp->t_col--;
oobp->b_wptr--;
goto outofbufs;
}
c = omaptab[c];
}
/*
* If no other output processing is required,
* push the character into the block and
* get another.
*/
if (!(tp->t_modes.c_oflag & OPOST)) {
tp->t_col++;
*obp->b_wptr++ = c;
chars_left--;
continue;
}
/*
* OPOST output flag is set.
* Map lower case to upper case if OLCUC flag
* is set.
*/
if ((tp->t_modes.c_oflag & OLCUC) &&
c >= 'a' && c <= 'z')
c -= 'a' - 'A';
} else {
/*
* Copy all the ordinary characters,
* possibly mapping upper case
* to lower case.
*/
register int chars_to_move;
register int chars_moved;
chars_to_move = ibp->b_wptr - ibp->b_rptr;
if (chars_to_move > chars_left)
chars_to_move = chars_left;
chars_moved = movtuc(chars_to_move,
ibp->b_rptr, obp->b_wptr,
(tp->t_modes.c_oflag & OLCUC ?
lcuctab : notrantab));
tp->t_col += chars_moved;
ibp->b_rptr += chars_moved;
obp->b_wptr += chars_moved;
chars_left -= chars_moved;
if (ibp->b_rptr >= ibp->b_wptr) {
/* moved all of block */
continue;
}
if (chars_left == 0 && !NEW_BLOCK())
goto outofbufs;
c = *ibp->b_rptr++; /* stopper */
}
/*
* Map <CR> to <NL> on output if OCRNL flag set.
*/
if (c == '\r' && (tp->t_modes.c_oflag & OCRNL))
c = '\n';
ctype = typetab[c];
/*
* Map <NL> to <CR><NL> on output if ONLCR flag is set.
*/
if (c == '\n' && (tp->t_modes.c_oflag & ONLCR)) {
register int s;
s = splstr();
if (!(tp->t_state & TS_TTCR)) {
tp->t_state |= TS_TTCR;
c = '\r';
ctype = typetab['\r'];
--ibp->b_rptr;
} else
tp->t_state &= ~TS_TTCR;
(void) splx(s);
}
/*
* Delay values and column position calculated here.
*/
count = 0;
switch (ctype) {
case PRINTABLE:
tp->t_col++;
*obp->b_wptr++ = c;
chars_left--;
break;
case CONTROL:
*obp->b_wptr++ = c;
chars_left--;
break;
case BACKSPACE:
if (tp->t_col)
tp->t_col--;
if (tp->t_modes.c_oflag & BSDLY) {
if (tp->t_modes.c_oflag & OFILL)
count = 2;
else
count = 3;
}
*obp->b_wptr++ = c;
chars_left--;
break;
case NEWLINE:
if (tp->t_modes.c_oflag & ONLRET)
goto cr;
if ((tp->t_modes.c_oflag & NLDLY) == NL1)
count = 2;
*obp->b_wptr++ = c;
chars_left--;
break;
case TAB:
/*
* Map '\t' to spaces if XTABS flag is set.
*/
if ((tp->t_modes.c_oflag & TABDLY) == XTABS) {
for (;;) {
*obp->b_wptr++ = ' ';
chars_left--;
tp->t_col++;
if ((tp->t_col & 07) == 0)
break; /* every 8th */
/*
* If we don't have room to
* fully expand this tab in
* this block, back up to
* continue expanding it
* into the next block.
*/
if (obp->b_wptr >=
obp->b_datap->db_lim) {
ibp->b_rptr--;
break;
}
}
} else {
tp->t_col |= 07;
tp->t_col++;
if (tp->t_modes.c_oflag & OFILL) {
if (tp->t_modes.c_oflag&TABDLY)
count = 2;
} else {
switch (tp->t_modes.c_oflag&TABDLY) {
case TAB2:
count = 6;
break;
case TAB1:
count = 1 +
(tp->t_col | ~07);
if (count < 5)
count = 0;
break;
}
}
*obp->b_wptr++ = c;
chars_left--;
}
break;
case VTAB:
if ((tp->t_modes.c_oflag & VTDLY) &&
!(tp->t_modes.c_oflag & OFILL))
count = 127;
*obp->b_wptr++ = c;
chars_left--;
break;
case RETURN:
/*
* Ignore <CR> in column 0 if ONOCR flag set.
*/
if (tp->t_col == 0 &&
(tp->t_modes.c_oflag & ONOCR))
break;
cr:
switch (tp->t_modes.c_oflag & CRDLY) {
case CR1:
if (tp->t_modes.c_oflag & OFILL)
count = 2;
else
count = tp->t_col % 2;
break;
case CR2:
if (tp->t_modes.c_oflag & OFILL)
count = 4;
else
count = 6;
break;
case CR3:
if (tp->t_modes.c_oflag & OFILL)
count = 0;
else
count = 9;
break;
}
tp->t_col = 0;
*obp->b_wptr++ = c;
chars_left--;
break;
}
if (count != 0) {
/*
* At this point, the characters we've added
* to the output message are consistent with
* those we've processed from the input
* message. If an allocation failure occurs
* while handling the delay, it's too hard to
* back up to undo the input and output
* characters associated with the delay. This
* leaves us with two choices: punt (and drop
* the remaining delay) or add more state to
* the ldterm_state structure that records the
* value of count.
*
* In the second alternative, we would have to
* check the saved count value on entry to
* this routine (and quite likely in lots of
* other places, too) and jump here if it's
* nonzero (after suitable preparation).
*
* The second alternative looks too ugly for
* too little benefit, so we choose the
* first.
*/
if (tp->t_modes.c_oflag & OFILL) {
do {
if (chars_left == 0 &&
!NEW_BLOCK())
goto outofbufs;
if (tp->t_modes.c_oflag & OFDEL)
*obp->b_wptr++ = CDEL;
else
*obp->b_wptr++ = CNUL;
chars_left--;
} while (--count != 0);
} else {
if (!(tp->t_modes.c_lflag & FLUSHO)) {
tk_nout += msgdsize(*omp);
putnext(q, *omp);
(void) putctl1(q->q_next,
M_DELAY, count);
} else {
/* Flush it. */
freemsg(*omp);
}
chars_left = 0;
/*
* We have to start a new message;
* the delay introduces a break
* between messages.
*/
*omp = NULL;
contpp = omp;
}
}
}
/*
* Free the current input block and advance to the next.
*/
freeb(ibp);
ibp = cibp;
if (ibp == NULL)
break;
}
outofbufs:
return (ibp);
#undef NEW_BLOCK
}
#if !defined(vax) && !defined(sun)
movtuc(size, from, origto, table)
register int size;
register unsigned char *from;
unsigned char *origto;
register unsigned char *table;
{
register unsigned char *to = origto;
register unsigned char c;
while (size != 0 && (c = table[*from++]) != 0) {
*to++ = c;
size--;
}
return (to - origto);
}
#endif
/*
* Respond to the "flush output" character; if output is being flushed, stop
* flushing it, otherwise flush our write queue and the write queues below us,
* echo the "flush output" character, and start flushing subsequent output.
*/
static void
do_flush_output(c, q, tp)
unsigned char c;
register queue_t *q;
register ldterm_state_t *tp;
{
if (tp->t_modes.c_lflag & FLUSHO)
tp->t_modes.c_lflag &= ~FLUSHO;
else {
flushq(q, FLUSHDATA); /* flush our write queue */
/* flush the ones below us */
(void) putctl1(q->q_next, M_FLUSH, FLUSHW);
if ((tp->t_echomp = allocb(EBSIZE, BPRI_HI)) != NULL) {
(void) echo_char(c, q, 1, tp);
if (tp->t_msglen != 0)
curline_reprint(q, EBSIZE, tp);
if (tp->t_echomp != NULL) {
putnext(q, tp->t_echomp);
tp->t_echomp = NULL;
}
}
tp->t_modes.c_lflag |= FLUSHO;
}
}
/*
* Respond to a signal-generating character or an M_BREAK message by echoing
* the signal-generating character (if "doecho" is set) and sending an M_PCSIG
* and M_FLUSH message upstream.
*/
static void
do_signal(q, sig, c, doecho, ignore_noflsh)
register queue_t *q;
int sig;
unsigned char c;
int doecho;
int ignore_noflsh;
{
register ldterm_state_t *tp = (ldterm_state_t *)q->q_ptr;
/*
* If ignore_noflsh, do the flushing regardless of NOFLSH.
*/
if (ignore_noflsh || (!(tp->t_modes.c_lflag & NOFLSH))) {
/*
* Put it on our read queue, so the service procedure
* will see it.
*/
if (sig != SIGTSTP) {
/*
* Flush our read and write queues, and send a
* "flush read and write" message downstream and
* upstream.
*/
flushq(WR(q), FLUSHDATA);
flushq(q, FLUSHDATA);
(void) putctl1(WR(q)->q_next, M_FLUSH, FLUSHRW);
(void) putctl1(q->q_next, M_FLUSH, FLUSHRW);
} else {
/*
* Flush our read queue, and send a "flush read"
* message upstream and downstream.
*/
flushq(q, FLUSHDATA);
(void) putctl1(WR(q)->q_next, M_FLUSH, FLUSHR);
(void) putctl1(q->q_next, M_FLUSH, FLUSHR);
}
}
tp->t_state &= ~TS_QUOT;
/* PCSIG, not SIG - do it NOW */
(void) putctl1(q->q_next, M_PCSIG, sig);
if (doecho) {
if ((tp->t_echomp = allocb(4, BPRI_HI)) != NULL) {
(void) echo_char(c, WR(q), 4, tp);
putnext(WR(q), tp->t_echomp);
tp->t_echomp = NULL;
}
}
}
/*
* Called when an M_IOCTL message is seen on the write queue; does whatever
* we're supposed to do with it, and either replies immediately or passes it
* to the next module down.
*/
static void
do_ioctl(q, mp)
queue_t *q;
register mblk_t *mp;
{
register ldterm_state_t *tp;
register struct iocblk *iocp;
iocp = (struct iocblk *)mp->b_rptr;
tp = (ldterm_state_t *)q->q_ptr;
switch (iocp->ioc_cmd) {
case TCSETS:
case TCSETSW:
case TCSETSF: {
/*
* Set current parameters and special characters.
*/
register struct termios *cb =
(struct termios *)mp->b_cont->b_rptr;
struct termios oldmodes;
oldmodes = tp->t_modes;
tp->t_modes = *cb;
if (tp->t_modes.c_lflag & PENDIN) {
/*
* Yuk. The C shell file completion code actually
* uses this "feature", so we have to support it.
*/
if (tp->t_message != NULL) {
tp->t_state |= TS_RESCAN;
qenable(RD(q));
}
tp->t_modes.c_lflag &= ~PENDIN;
}
chgstropts(&oldmodes, tp, RD(q));
/*
* The driver may want to know about the following iflags:
* IGNBRK, BRKINT, IGNPAR, PARMRK, INPCK.
*/
break;
}
case TCSETA:
case TCSETAW:
case TCSETAF: {
/*
* Old-style "ioctl" to set current parameters and
* special characters.
* Don't clear out the unset portions, leave them as
* they are.
*/
register struct termio *cb =
(struct termio *)mp->b_cont->b_rptr;
struct termios oldmodes;
oldmodes = tp->t_modes;
tp->t_modes.c_iflag =
(tp->t_modes.c_iflag & 0xffff0000 | cb->c_iflag);
tp->t_modes.c_oflag =
(tp->t_modes.c_oflag & 0xffff0000 | cb->c_oflag);
tp->t_modes.c_cflag =
(tp->t_modes.c_cflag & 0xffff0000 | cb->c_cflag);
tp->t_modes.c_lflag =
(tp->t_modes.c_lflag & 0xffff0000 | cb->c_lflag);
tp->t_modes.c_cc[VINTR] = cb->c_cc[VINTR];
tp->t_modes.c_cc[VQUIT] = cb->c_cc[VQUIT];
tp->t_modes.c_cc[VERASE] = cb->c_cc[VERASE];
tp->t_modes.c_cc[VKILL] = cb->c_cc[VKILL];
tp->t_modes.c_cc[VEOF] = cb->c_cc[VEOF];
tp->t_modes.c_cc[VEOL] = cb->c_cc[VEOL];
tp->t_modes.c_cc[VEOL2] = cb->c_cc[VEOL2];
tp->t_modes.c_cc[VSWTCH] = cb->c_cc[VSWTCH];
chgstropts(&oldmodes, tp, RD(q));
/*
* The driver may want to know about the following iflags:
* IGNBRK, BRKINT, IGNPAR, PARMRK, INPCK.
*/
break;
}
case TCFLSH:
/*
* Do the flush on the write queue immediately, and queue
* up any flush on the read queue for the service procedure
* to see. Then turn it into the appropriate M_FLUSH message,
* so that the module below us doesn't have to know about
* TCFLSH.
*/
ASSERT(mp->b_datap != NULL);
switch (*(int *)mp->b_cont->b_rptr) {
default:
u.u_error = EINVAL;
break;
case TCIFLUSH:
(void) putctl1(q->q_next, M_FLUSH, FLUSHR);
(void) putctl1(RD(q), M_FLUSH, FLUSHR);
break;
case TCOFLUSH:
flushq(q, FLUSHDATA);
(void) putctl1(q->q_next, M_FLUSH, FLUSHW);
(void) putctl1(RD(q)->q_next, M_FLUSH, FLUSHW);
break;
case TCIOFLUSH:
flushq(q, FLUSHDATA);
(void) putctl1(q->q_next, M_FLUSH, FLUSHRW);
(void) putctl1(RD(q), M_FLUSH, FLUSHRW);
break;
}
mp->b_datap->db_type = M_IOCACK;
iocp->ioc_rval = 0;
iocp->ioc_count = 0;
qreply(q, mp);
return;
case TCXONC:
switch (*(int *)mp->b_cont->b_rptr) {
case TCOOFF:
(void) putctl(q->q_next, M_STOP);
tp->t_state |= TS_TTSTOP;
break;
case TCOON:
(void) putctl(q->q_next, M_START);
tp->t_state &= ~TS_TTSTOP;
break;
case TCIOFF:
(void) putctl(q->q_next, M_STOPI);
tp->t_state |= TS_TBLOCK;
break;
case TCION:
(void) putctl(q->q_next, M_STARTI);
tp->t_state &= ~TS_TBLOCK;
break;
}
mp->b_datap->db_type = M_IOCACK;
iocp->ioc_rval = 0;
iocp->ioc_count = 0;
qreply(q, mp);
return;
}
putnext(q, mp);
}
/*
* Send an M_SETOPTS message upstream if any mode changes are being made
* that affect the stream head options.
*/
static void
chgstropts(oldmodep, tp, q)
register struct termios *oldmodep;
register ldterm_state_t *tp;
queue_t *q;
{
struct stroptions optbuf;
register mblk_t *bp;
optbuf.so_flags = 0;
if ((oldmodep->c_lflag ^ tp->t_modes.c_lflag) & ICANON) {
/*
* Canonical mode is changing state; switch the stream head
* to message-nondiscard or byte-stream mode. Also, rerun
* the service procedure so it can change its mind about
* whether to send data upstream or not.
*/
optbuf.so_flags = SO_READOPT|SO_VMIN|SO_VTIME;
if (tp->t_modes.c_lflag & ICANON) {
optbuf.so_readopt = RMSGN;
optbuf.so_vmin = (ushort) -1;
optbuf.so_vtime = 0;
} else {
optbuf.so_readopt = RNORM;
optbuf.so_vmin = tp->t_modes.c_cc[VMIN];
optbuf.so_vtime = tp->t_modes.c_cc[VTIME];
}
if (tp->t_message != NULL) {
tp->t_state |= TS_RESCAN;
qenable(q);
}
} else if (!(tp->t_modes.c_lflag & ICANON) &&
(oldmodep->c_cc[VMIN] != tp->t_modes.c_cc[VMIN] ||
oldmodep->c_cc[VTIME] != tp->t_modes.c_cc[VTIME])) {
/*
* Canonical mode is off, and the VMIN and VTIME values are
* changing; let the stream head know.
*/
optbuf.so_flags = SO_VMIN|SO_VTIME;
optbuf.so_vmin = tp->t_modes.c_cc[VMIN];
optbuf.so_vtime = tp->t_modes.c_cc[VTIME];
}
if ((oldmodep->c_lflag ^ tp->t_modes.c_lflag) & TOSTOP) {
/*
* The "stop on background write" bit is changing.
*/
optbuf.so_flags |= SO_TOSTOP;
if (tp->t_modes.c_lflag & TOSTOP)
optbuf.so_tostop = 1;
else
optbuf.so_tostop = 0;
}
if (optbuf.so_flags != 0) {
if ((bp = allocb(sizeof (struct stroptions), BPRI_HI)) == NULL)
panic("chgstropts: can't allocate stroptions message");
/* XXX - should probably do bufcall */
*(struct stroptions *)bp->b_wptr = optbuf;
bp->b_wptr += sizeof (struct stroptions);
bp->b_datap->db_type = M_SETOPTS;
putnext(q, bp);
}
}
/*
* Called when an M_IOCACK message is seen on the read queue; modifies
* the data being returned, if necessary, and passes the reply up.
*/
static void
ioctl_reply(q, mp)
queue_t *q;
register mblk_t *mp;
{
register ldterm_state_t *tp;
register struct iocblk *iocp;
iocp = (struct iocblk *)mp->b_rptr;
tp = (ldterm_state_t *)q->q_ptr;
switch (iocp->ioc_cmd) {
case TCGETS: {
/*
* Get current parameters and return them to stream head
* eventually.
*/
register struct termios *cb =
(struct termios *)mp->b_cont->b_rptr;
register unsigned long cflag = cb->c_cflag;
*cb = tp->t_modes;
if (cflag != 0)
cb->c_cflag = cflag; /* set by driver */
break;
}
case TCGETA: {
/*
* Old-style "ioctl" to get current parameters and
* return them to stream head eventually.
*/
register struct termio *cb =
(struct termio *)mp->b_cont->b_rptr;
cb->c_iflag = tp->t_modes.c_iflag; /* all except the */
cb->c_oflag = tp->t_modes.c_oflag; /* cb->c_cflag */
cb->c_lflag = tp->t_modes.c_lflag;
if (cb->c_cflag == 0) /* not set by driver */
cb->c_cflag = tp->t_modes.c_cflag;
cb->c_line = 0;
cb->c_cc[VINTR] = tp->t_modes.c_cc[VINTR];
cb->c_cc[VQUIT] = tp->t_modes.c_cc[VQUIT];
cb->c_cc[VERASE] = tp->t_modes.c_cc[VERASE];
cb->c_cc[VKILL] = tp->t_modes.c_cc[VKILL];
cb->c_cc[VEOF] = tp->t_modes.c_cc[VEOF];
cb->c_cc[VEOL] = tp->t_modes.c_cc[VEOL];
cb->c_cc[VEOL2] = tp->t_modes.c_cc[VEOL2];
cb->c_cc[VSWTCH] = tp->t_modes.c_cc[VSWTCH];
break;
}
}
putnext(q, mp);
}