blob: ff36dab9488dfad59b2d3a0631aaa3c02132d489 [file] [log] [blame]
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
* Copyright (c) 2014, Joyent, Inc. All rights reserved.
* Copyright 2018 OmniOS Community Edition (OmniOSce) Association.
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
/*
* University Copyright- Copyright (c) 1982, 1986, 1988
* The Regents of the University of California
* All Rights Reserved
*
* University Acknowledgment- Portions of this document are derived from
* software developed by the University of California, Berkeley, and its
* contributors.
*/
/*
* Standard Streams Terminal Line Discipline module.
*/
#include <sys/param.h>
#include <sys/types.h>
#include <sys/termio.h>
#include <sys/stream.h>
#include <sys/conf.h>
#include <sys/stropts.h>
#include <sys/strsubr.h>
#include <sys/strsun.h>
#include <sys/strtty.h>
#include <sys/signal.h>
#include <sys/file.h>
#include <sys/errno.h>
#include <sys/debug.h>
#include <sys/cmn_err.h>
#include <sys/euc.h>
#include <sys/eucioctl.h>
#include <sys/csiioctl.h>
#include <sys/ptms.h>
#include <sys/ldterm.h>
#include <sys/cred.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/kmem.h>
#include <sys/modctl.h>
/* Time limit when draining during a close(9E) invoked by exit(2) */
/* Can be set to zero to emulate the old, broken behavior */
int ldterm_drain_limit = 15000000;
/*
* Character types.
*/
#define ORDINARY 0
#define CONTROL 1
#define BACKSPACE 2
#define NEWLINE 3
#define TAB 4
#define VTAB 5
#define RETURN 6
/*
* The following for EUC handling:
*/
#define T_SS2 7
#define T_SS3 8
/*
* Table indicating character classes to tty driver. In particular,
* if the class is ORDINARY, 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 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 044 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 050 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 054 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 060 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 064 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 070 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 074 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 100 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 104 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 110 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 114 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 120 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 124 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 130 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 134 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 140 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 144 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 150 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 154 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 160 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 164 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 170 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 174 */ ORDINARY, ORDINARY, ORDINARY, CONTROL,
/* 200 */ CONTROL, CONTROL, CONTROL, CONTROL,
/* 204 */ CONTROL, CONTROL, T_SS2, T_SS3,
/* 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 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 244 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 250 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 254 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 260 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 264 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 270 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 274 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 300 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 304 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 310 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 314 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 320 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 324 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 330 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 334 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 340 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 344 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 350 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 354 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 360 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 364 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/* 370 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
/*
* WARNING: For EUC, 0xFF must be an ordinary character. It is used with
* single-byte EUC in some of the "ISO Latin Alphabet" codesets, and occupies
* a screen position; in those ISO sets where that position isn't used, it
* shouldn't make any difference.
*/
/* 374 */ ORDINARY, ORDINARY, ORDINARY, ORDINARY,
};
/*
* Translation table for output without OLCUC. All ORDINARY-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 */ 0240, 0241, 0242, 0243, 0244, 0245, 0246, 0247,
/* 250 */ 0250, 0251, 0252, 0253, 0254, 0255, 0256, 0257,
/* 260 */ 0260, 0261, 0262, 0263, 0264, 0265, 0266, 0267,
/* 270 */ 0270, 0271, 0272, 0273, 0274, 0275, 0276, 0277,
/* 300 */ 0300, 0301, 0302, 0303, 0304, 0305, 0306, 0307,
/* 310 */ 0310, 0311, 0312, 0313, 0314, 0315, 0316, 0317,
/* 320 */ 0320, 0321, 0322, 0323, 0324, 0325, 0326, 0327,
/* 330 */ 0330, 0331, 0332, 0333, 0334, 0335, 0336, 0337,
/* 340 */ 0340, 0341, 0342, 0343, 0344, 0345, 0346, 0347,
/* 350 */ 0350, 0351, 0352, 0353, 0354, 0355, 0356, 0357,
/* 360 */ 0360, 0361, 0362, 0363, 0364, 0365, 0366, 0367,
/*
* WARNING: as for above ISO sets, \377 may be used. Translate it to
* itself.
*/
/* 370 */ 0370, 0371, 0372, 0373, 0374, 0375, 0376, 0377,
};
/*
* Translation table for output with OLCUC. All ORDINARY-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 */ 0240, 0241, 0242, 0243, 0244, 0245, 0246, 0247,
/* 250 */ 0250, 0251, 0252, 0253, 0254, 0255, 0256, 0257,
/* 260 */ 0260, 0261, 0262, 0263, 0264, 0265, 0266, 0267,
/* 270 */ 0270, 0271, 0272, 0273, 0274, 0275, 0276, 0277,
/* 300 */ 0300, 0301, 0302, 0303, 0304, 0305, 0306, 0307,
/* 310 */ 0310, 0311, 0312, 0313, 0314, 0315, 0316, 0317,
/* 320 */ 0320, 0321, 0322, 0323, 0324, 0325, 0326, 0327,
/* 330 */ 0330, 0331, 0332, 0333, 0334, 0335, 0336, 0337,
/* 340 */ 0340, 0341, 0342, 0343, 0344, 0345, 0346, 0347,
/* 350 */ 0350, 0351, 0352, 0353, 0354, 0355, 0356, 0357,
/* 360 */ 0360, 0361, 0362, 0363, 0364, 0365, 0366, 0367,
/*
* WARNING: as for above ISO sets, \377 may be used. Translate it to
* itself.
*/
/* 370 */ 0370, 0371, 0372, 0373, 0374, 0375, 0376, 0377,
};
/*
* 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 */
};
/*
* Translation table for TS_MEUC output without OLCUC. All printing ASCII
* characters translate to themselves. All other _bytes_ have a zero in
* the table, which stops the copying. This and the following table exist
* only so we can use the existing movtuc processing with or without OLCUC.
* Maybe it speeds up something...because we can copy a block of characters
* by only looking for zeros in the table.
*
* If we took the simple expedient of DISALLOWING "olcuc" with multi-byte
* processing, we could rid ourselves of both these tables and save 512 bytes;
* seriously, it doesn't make much sense to use olcuc with multi-byte, and
* it will probably never be used. Consideration should be given to disallowing
* the combination TS_MEUC & OLCUC.
*/
static unsigned char enotrantab[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 - 377 aren't mapped (they're stoppers). */
};
/*
* Translation table for TS_MEUC output with OLCUC. All printing ASCII
* translate to themselves, except for lower-case letters which translate
* to their upper-case equivalents. All other bytes have a zero in
* the table, which stops the copying. Useless for ISO Latin Alphabet
* translations, but *sigh* OLCUC is really only defined for ASCII anyway.
* We only have this table so we can use the existing OLCUC processing with
* TS_MEUC set (multi-byte mode). Nobody would ever think of actually
* _using_ it...would they?
*/
static unsigned char elcuctab[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 - 377 aren't mapped (they're stoppers). */
};
static struct streamtab ldtrinfo;
static struct fmodsw fsw = {
"ldterm",
&ldtrinfo,
D_MTQPAIR | D_MP | _D_SINGLE_INSTANCE
};
static struct modlstrmod modlstrmod = {
&mod_strmodops, "terminal line discipline", &fsw
};
static struct modlinkage modlinkage = {
MODREV_1, &modlstrmod, NULL
};
int
_init(void)
{
return (mod_install(&modlinkage));
}
int
_fini(void)
{
return (mod_remove(&modlinkage));
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
static int ldtermopen(queue_t *, dev_t *, int, int, cred_t *);
static int ldtermclose(queue_t *, int, cred_t *);
static void ldtermrput(queue_t *, mblk_t *);
static void ldtermrsrv(queue_t *);
static int ldtermrmsg(queue_t *, mblk_t *);
static void ldtermwput(queue_t *, mblk_t *);
static void ldtermwsrv(queue_t *);
static int ldtermwmsg(queue_t *, mblk_t *);
static mblk_t *ldterm_docanon(unsigned char, mblk_t *, size_t, queue_t *,
ldtermstd_state_t *, int *);
static int ldterm_unget(ldtermstd_state_t *);
static void ldterm_trim(ldtermstd_state_t *);
static void ldterm_rubout(unsigned char, queue_t *, size_t,
ldtermstd_state_t *);
static int ldterm_tabcols(ldtermstd_state_t *);
static void ldterm_erase(queue_t *, size_t, ldtermstd_state_t *);
static void ldterm_werase(queue_t *, size_t, ldtermstd_state_t *);
static void ldterm_kill(queue_t *, size_t, ldtermstd_state_t *);
static void ldterm_reprint(queue_t *, size_t, ldtermstd_state_t *);
static mblk_t *ldterm_dononcanon(mblk_t *, mblk_t *, size_t, queue_t *,
ldtermstd_state_t *);
static int ldterm_echo(unsigned char, queue_t *, size_t,
ldtermstd_state_t *);
static void ldterm_outchar(unsigned char, queue_t *, size_t,
ldtermstd_state_t *);
static void ldterm_outstring(unsigned char *, int, queue_t *, size_t,
ldtermstd_state_t *tp);
static mblk_t *newmsg(ldtermstd_state_t *);
static void ldterm_msg_upstream(queue_t *, ldtermstd_state_t *);
static void ldterm_wenable(void *);
static mblk_t *ldterm_output_msg(queue_t *, mblk_t *, mblk_t **,
ldtermstd_state_t *, size_t, int);
static void ldterm_flush_output(unsigned char, queue_t *,
ldtermstd_state_t *);
static void ldterm_dosig(queue_t *, int, unsigned char, int, int);
static void ldterm_do_ioctl(queue_t *, mblk_t *);
static int chgstropts(struct termios *, ldtermstd_state_t *, queue_t *);
static void ldterm_ioctl_reply(queue_t *, mblk_t *);
static void vmin_satisfied(queue_t *, ldtermstd_state_t *, int);
static void vmin_settimer(queue_t *);
static void vmin_timed_out(void *);
static void ldterm_adjust_modes(ldtermstd_state_t *);
static void ldterm_eucwarn(ldtermstd_state_t *);
static void cp_eucwioc(eucioc_t *, eucioc_t *, int);
static int ldterm_codeset(uchar_t, uchar_t);
static void ldterm_csi_erase(queue_t *, size_t, ldtermstd_state_t *);
static void ldterm_csi_werase(queue_t *, size_t, ldtermstd_state_t *);
static uchar_t ldterm_utf8_width(uchar_t *, int);
/* Codeset type specific methods for EUC, PCCS, and, UTF-8 codeset types. */
static int __ldterm_dispwidth_euc(uchar_t, void *, int);
static int __ldterm_memwidth_euc(uchar_t, void *);
static int __ldterm_dispwidth_pccs(uchar_t, void *, int);
static int __ldterm_memwidth_pccs(uchar_t, void *);
static int __ldterm_dispwidth_utf8(uchar_t, void *, int);
static int __ldterm_memwidth_utf8(uchar_t, void *);
static const ldterm_cs_methods_t cs_methods[LDTERM_CS_TYPE_MAX + 1] = {
{
NULL,
NULL
},
{
__ldterm_dispwidth_euc,
__ldterm_memwidth_euc
},
{
__ldterm_dispwidth_pccs,
__ldterm_memwidth_pccs
},
{
__ldterm_dispwidth_utf8,
__ldterm_memwidth_utf8
}
};
/*
* The default codeset is presumably C locale's ISO 646 in EUC but
* the data structure at below defined as the default codeset data also
* support any single byte (EUC) locales.
*/
static const ldterm_cs_data_t default_cs_data = {
LDTERM_DATA_VERSION,
LDTERM_CS_TYPE_EUC,
(uchar_t)0,
(uchar_t)0,
(char *)NULL,
{
'\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0'
}
};
/*
* The following tables are from either u8_textprep.c or uconv.c at
* usr/src/common/unicode/. The tables are used to figure out corresponding
* UTF-8 character byte lengths and also the validity of given character bytes.
*/
extern const int8_t u8_number_of_bytes[];
extern const uchar_t u8_masks_tbl[];
extern const uint8_t u8_valid_min_2nd_byte[];
extern const uint8_t u8_valid_max_2nd_byte[];
/*
* Unicode character width definition tables from uwidth.c:
*/
extern const ldterm_unicode_data_cell_t ldterm_ucode[][16384];
#ifdef LDDEBUG
int ldterm_debug = 0;
#define DEBUG1(a) if (ldterm_debug == 1) printf a
#define DEBUG2(a) if (ldterm_debug >= 2) printf a /* allocations */
#define DEBUG3(a) if (ldterm_debug >= 3) printf a /* M_CTL Stuff */
#define DEBUG4(a) if (ldterm_debug >= 4) printf a /* M_READ Stuff */
#define DEBUG5(a) if (ldterm_debug >= 5) printf a
#define DEBUG6(a) if (ldterm_debug >= 6) printf a
#define DEBUG7(a) if (ldterm_debug >= 7) printf a
#else
#define DEBUG1(a)
#define DEBUG2(a)
#define DEBUG3(a)
#define DEBUG4(a)
#define DEBUG5(a)
#define DEBUG6(a)
#define DEBUG7(a)
#endif /* LDDEBUG */
/*
* 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,
"ldterm",
0,
_TTY_BUFSIZ,
_TTY_BUFSIZ,
LOWAT
};
static struct qinit ldtermrinit = {
(int (*)())ldtermrput,
(int (*)())ldtermrsrv,
ldtermopen,
ldtermclose,
NULL,
&ldtermmiinfo
};
static struct module_info ldtermmoinfo = {
0x0bad,
"ldterm",
0,
INFPSZ,
1,
0
};
static struct qinit ldtermwinit = {
(int (*)())ldtermwput,
(int (*)())ldtermwsrv,
ldtermopen,
ldtermclose,
NULL,
&ldtermmoinfo
};
static struct streamtab ldtrinfo = {
&ldtermrinit,
&ldtermwinit,
NULL,
NULL
};
/*
* Dummy qbufcall callback routine used by open and close.
* The framework will wake up qwait_sig when we return from
* this routine (as part of leaving the perimeters.)
* (The framework enters the perimeters before calling the qbufcall() callback
* and leaves the perimeters after the callback routine has executed. The
* framework performs an implicit wakeup of any thread in qwait/qwait_sig
* when it leaves the perimeter. See qwait(9E).)
*/
/* ARGSUSED */
static void
dummy_callback(void *arg)
{}
static mblk_t *
open_ioctl(queue_t *q, uint_t cmd)
{
mblk_t *mp;
bufcall_id_t id;
int retv;
while ((mp = mkiocb(cmd)) == NULL) {
id = qbufcall(q, sizeof (struct iocblk), BPRI_MED,
dummy_callback, NULL);
retv = qwait_sig(q);
qunbufcall(q, id);
if (retv == 0)
break;
}
return (mp);
}
static mblk_t *
open_mblk(queue_t *q, size_t len)
{
mblk_t *mp;
bufcall_id_t id;
int retv;
while ((mp = allocb(len, BPRI_MED)) == NULL) {
id = qbufcall(q, len, BPRI_MED, dummy_callback, NULL);
retv = qwait_sig(q);
qunbufcall(q, id);
if (retv == 0)
break;
}
return (mp);
}
/*
* Line discipline open.
*/
/* ARGSUSED1 */
static int
ldtermopen(queue_t *q, dev_t *devp, int oflag, int sflag, cred_t *crp)
{
ldtermstd_state_t *tp;
mblk_t *bp, *qryp;
int len;
struct stroptions *strop;
struct termios *termiosp;
queue_t *wq;
if (q->q_ptr != NULL) {
return (0); /* already attached */
}
tp = (ldtermstd_state_t *)kmem_zalloc(sizeof (ldtermstd_state_t),
KM_SLEEP);
/*
* Get termios defaults. These are stored as
* a property in the "options" node.
*/
if (ddi_getlongprop(DDI_DEV_T_ANY, ddi_root_node(), DDI_PROP_NOTPROM,
"ttymodes", (caddr_t)&termiosp, &len) == DDI_PROP_SUCCESS &&
len == sizeof (struct termios)) {
tp->t_modes = *termiosp;
tp->t_amodes = *termiosp;
kmem_free(termiosp, len);
} else {
/*
* Gack! Whine about it.
*/
cmn_err(CE_WARN, "ldterm: Couldn't get ttymodes property!");
}
bzero(&tp->t_dmodes, sizeof (struct termios));
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_rd_request = 0;
tp->t_echomp = NULL;
tp->t_iocid = 0;
tp->t_wbufcid = 0;
tp->t_vtid = 0;
q->q_ptr = (caddr_t)tp;
WR(q)->q_ptr = (caddr_t)tp;
/*
* The following for EUC and also non-EUC codesets:
*/
tp->t_codeset = tp->t_eucleft = tp->t_eucign = tp->t_scratch_len = 0;
bzero(&tp->eucwioc, EUCSIZE);
tp->eucwioc.eucw[0] = 1; /* ASCII mem & screen width */
tp->eucwioc.scrw[0] = 1;
tp->t_maxeuc = 1; /* the max len in bytes of an EUC char */
tp->t_eucp = NULL;
tp->t_eucp_mp = NULL;
tp->t_eucwarn = 0; /* no bad chars seen yet */
tp->t_csdata = default_cs_data;
tp->t_csmethods = cs_methods[LDTERM_CS_TYPE_EUC];
qprocson(q);
/*
* Find out if the module below us does canonicalization; if
* so, we won't do it ourselves.
*/
if ((qryp = open_ioctl(q, MC_CANONQUERY)) == NULL)
goto open_abort;
/*
* Reformulate as an M_CTL message. The actual data will
* be in the b_cont field.
*/
qryp->b_datap->db_type = M_CTL;
wq = OTHERQ(q);
putnext(wq, qryp);
/* allocate a TCSBRK ioctl in case we'll need it on close */
if ((qryp = open_ioctl(q, TCSBRK)) == NULL)
goto open_abort;
tp->t_drainmsg = qryp;
if ((bp = open_mblk(q, sizeof (int))) == NULL)
goto open_abort;
qryp->b_cont = bp;
/*
* Find out if the underlying driver supports proper POSIX close
* semantics. If not, we'll have to approximate it using TCSBRK. If
* it does, it will respond with MC_HAS_POSIX, and we'll catch that in
* the ldtermrput routine.
*
* When the ldterm_drain_limit tunable is set to zero, we behave the
* same as old ldterm: don't send this new message, and always use
* TCSBRK during close.
*/
if (ldterm_drain_limit != 0) {
if ((qryp = open_ioctl(q, MC_POSIXQUERY)) == NULL)
goto open_abort;
qryp->b_datap->db_type = M_CTL;
putnext(wq, qryp);
}
/* prepare to clear the water marks on close */
if ((bp = open_mblk(q, sizeof (struct stroptions))) == NULL)
goto open_abort;
tp->t_closeopts = bp;
/*
* Set the high-water and low-water marks on the stream head
* to values appropriate for a terminal. Also set the "vmin"
* and "vtime" values to 1 and 0, turn on message-nondiscard
* mode (as we're in ICANON mode), and turn on "old-style
* NODELAY" mode.
*/
if ((bp = open_mblk(q, sizeof (struct stroptions))) == NULL)
goto open_abort;
strop = (struct stroptions *)bp->b_wptr;
strop->so_flags = SO_READOPT|SO_HIWAT|SO_LOWAT|SO_NDELON|SO_ISTTY;
strop->so_readopt = RMSGN;
strop->so_hiwat = _TTY_BUFSIZ;
strop->so_lowat = LOWAT;
bp->b_wptr += sizeof (struct stroptions);
bp->b_datap->db_type = M_SETOPTS;
putnext(q, bp);
return (0); /* this can become a controlling TTY */
open_abort:
qprocsoff(q);
q->q_ptr = NULL;
WR(q)->q_ptr = NULL;
freemsg(tp->t_closeopts);
freemsg(tp->t_drainmsg);
/* Dump the state structure */
kmem_free(tp, sizeof (ldtermstd_state_t));
return (EINTR);
}
struct close_timer {
timeout_id_t id;
ldtermstd_state_t *tp;
};
static void
drain_timed_out(void *arg)
{
struct close_timer *ctp = arg;
ctp->id = 0;
ctp->tp->t_state &= ~TS_IOCWAIT;
}
/* ARGSUSED2 */
static int
ldtermclose(queue_t *q, int cflag, cred_t *crp)
{
ldtermstd_state_t *tp = (ldtermstd_state_t *)q->q_ptr;
struct stroptions *strop;
mblk_t *bp;
struct close_timer cltimer;
/*
* If we have an outstanding vmin timeout, cancel it.
*/
tp->t_state |= TS_CLOSE;
if (tp->t_vtid != 0)
(void) quntimeout(q, tp->t_vtid);
tp->t_vtid = 0;
/*
* Cancel outstanding qbufcall request.
*/
if (tp->t_wbufcid != 0)
qunbufcall(q, tp->t_wbufcid);
/*
* Reset the high-water and low-water marks on the stream
* head (?), turn on byte-stream mode, and turn off
* "old-style NODELAY" mode.
*/
bp = tp->t_closeopts;
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);
if (cflag & (FNDELAY|FNONBLOCK)) {
freemsg(tp->t_drainmsg);
} else if ((bp = tp->t_drainmsg) != NULL) {
struct iocblk *iocb;
/*
* If the driver isn't known to have POSIX close semantics,
* then we have to emulate this the old way. This is done by
* sending down TCSBRK,1 to drain the output and waiting for
* the reply.
*/
iocb = (struct iocblk *)bp->b_rptr;
iocb->ioc_count = sizeof (int);
*(int *)bp->b_cont->b_rptr = 1;
bp->b_cont->b_wptr += sizeof (int);
tp->t_state |= TS_IOCWAIT;
tp->t_iocid = iocb->ioc_id;
if (!putq(WR(q), bp))
putnext(WR(q), bp);
/*
* If we're not able to receive signals at this point, then
* launch a timer. This timer will prevent us from waiting
* forever for a signal that won't arrive.
*/
cltimer.id = 0;
if (!ddi_can_receive_sig() && ldterm_drain_limit != 0) {
cltimer.tp = tp;
cltimer.id = qtimeout(q, drain_timed_out, &cltimer,
drv_usectohz(ldterm_drain_limit));
}
/*
* Note that the read side of ldterm and the qtimeout are
* protected by D_MTQPAIR, so no additional locking is needed
* here.
*/
while (tp->t_state & TS_IOCWAIT) {
if (qwait_sig(q) == 0)
break;
}
if (cltimer.id != 0)
(void) quntimeout(q, cltimer.id);
}
/*
* From here to the end, the routine does not sleep and does not
* reference STREAMS, so it's guaranteed to run to completion.
*/
qprocsoff(q);
freemsg(tp->t_message);
freemsg(tp->t_eucp_mp);
/* Dump the state structure, then unlink it */
if (tp->t_csdata.locale_name != NULL)
kmem_free(tp->t_csdata.locale_name,
strlen(tp->t_csdata.locale_name) + 1);
kmem_free(tp, sizeof (ldtermstd_state_t));
q->q_ptr = NULL;
return (0);
}
/*
* Put procedure for input from driver end of stream (read queue).
*/
static void
ldtermrput(queue_t *q, mblk_t *mp)
{
ldtermstd_state_t *tp;
unsigned char c;
queue_t *wrq = WR(q); /* write queue of ldterm mod */
queue_t *nextq = q->q_next; /* queue below us */
mblk_t *bp;
struct iocblk *qryp;
unsigned char *readp;
unsigned char *writep;
struct termios *emodes; /* effective modes set by driver */
int dbtype;
tp = (ldtermstd_state_t *)q->q_ptr;
/*
* We received our ack from the driver saying there is nothing left to
* shovel out, so wake up the close routine.
*/
dbtype = DB_TYPE(mp);
if ((dbtype == M_IOCACK || dbtype == M_IOCNAK) &&
(tp->t_state & (TS_CLOSE|TS_IOCWAIT)) == (TS_CLOSE|TS_IOCWAIT)) {
struct iocblk *iocp = (struct iocblk *)mp->b_rptr;
if (iocp->ioc_id == tp->t_iocid) {
tp->t_state &= ~TS_IOCWAIT;
freemsg(mp);
return;
}
}
switch (dbtype) {
default:
(void) putq(q, mp);
return;
/*
* Send these up unmolested
*
*/
case M_PCSIG:
case M_SIG:
case M_IOCNAK:
putnext(q, mp);
return;
case M_IOCACK:
ldterm_ioctl_reply(q, mp);
return;
case M_BREAK:
/*
* Parity errors are sent up as M_BREAKS with single
* character data (formerly handled in the driver)
*/
if (mp->b_wptr - mp->b_rptr == 1) {
/*
* IGNPAR PARMRK RESULT
* off off 0
* off on 3 byte sequence
* on either ignored
*/
if (!(tp->t_amodes.c_iflag & IGNPAR)) {
mp->b_wptr = mp->b_rptr;
if (tp->t_amodes.c_iflag & PARMRK) {
unsigned char c;
c = *mp->b_rptr;
freemsg(mp);
if ((mp = allocb(3, BPRI_HI)) == NULL) {
cmn_err(CE_WARN,
"ldtermrput: no blocks");
return;
}
mp->b_datap->db_type = M_DATA;
*mp->b_wptr++ = (uchar_t)'\377';
*mp->b_wptr++ = '\0';
*mp->b_wptr++ = c;
putnext(q, mp);
} else {
mp->b_datap->db_type = M_DATA;
*mp->b_wptr++ = '\0';
putnext(q, mp);
}
} else {
freemsg(mp);
}
return;
}
/*
* We look at the apparent modes here instead of the
* effective modes. Effective modes cannot be used if
* IGNBRK, BRINT and PARMRK have been negotiated to
* be handled by the driver. Since M_BREAK should be
* sent upstream only if break processing was not
* already done, it should be ok to use the apparent
* modes.
*/
if (!(tp->t_amodes.c_iflag & IGNBRK)) {
if (tp->t_amodes.c_iflag & BRKINT) {
ldterm_dosig(q, SIGINT, '\0', M_PCSIG, FLUSHRW);
freemsg(mp);
} else if (tp->t_amodes.c_iflag & PARMRK) {
/*
* Send '\377','\0', '\0'.
*/
freemsg(mp);
if ((mp = allocb(3, BPRI_HI)) == NULL) {
cmn_err(CE_WARN,
"ldtermrput: no blocks");
return;
}
mp->b_datap->db_type = M_DATA;
*mp->b_wptr++ = (uchar_t)'\377';
*mp->b_wptr++ = '\0';
*mp->b_wptr++ = '\0';
putnext(q, mp);
} else {
/*
* Act as if a '\0' came in.
*/
freemsg(mp);
if ((mp = allocb(1, BPRI_HI)) == NULL) {
cmn_err(CE_WARN,
"ldtermrput: no blocks");
return;
}
mp->b_datap->db_type = M_DATA;
*mp->b_wptr++ = '\0';
putnext(q, mp);
}
} else {
freemsg(mp);
}
return;
case M_CTL:
DEBUG3(("ldtermrput: M_CTL received\n"));
/*
* The M_CTL has been standardized to look like an
* M_IOCTL message.
*/
if ((mp->b_wptr - mp->b_rptr) != sizeof (struct iocblk)) {
DEBUG3((
"Non standard M_CTL received by ldterm module\n"));
/* May be for someone else; pass it on */
putnext(q, mp);
return;
}
qryp = (struct iocblk *)mp->b_rptr;
switch (qryp->ioc_cmd) {
case MC_PART_CANON:
DEBUG3(("ldtermrput: M_CTL Query Reply\n"));
if (!mp->b_cont) {
DEBUG3(("No information in Query Message\n"));
break;
}
if ((mp->b_cont->b_wptr - mp->b_cont->b_rptr) ==
sizeof (struct termios)) {
DEBUG3(("ldtermrput: M_CTL GrandScheme\n"));
/* elaborate turning off scheme */
emodes = (struct termios *)mp->b_cont->b_rptr;
bcopy(emodes, &tp->t_dmodes,
sizeof (struct termios));
ldterm_adjust_modes(tp);
break;
} else {
DEBUG3(("Incorrect query replysize\n"));
break;
}
case MC_NO_CANON:
tp->t_state |= TS_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;
if (tp->t_state & TS_MEUC) {
ASSERT(tp->t_eucp_mp);
tp->t_eucp = tp->t_eucp_mp->b_rptr;
tp->t_codeset = 0;
tp->t_eucleft = 0;
}
}
break;
case MC_DO_CANON:
tp->t_state &= ~TS_NOCANON;
break;
case MC_HAS_POSIX:
/* no longer any reason to drain from ldterm */
if (ldterm_drain_limit != 0) {
freemsg(tp->t_drainmsg);
tp->t_drainmsg = NULL;
}
break;
default:
DEBUG3(("Unknown M_CTL Message\n"));
break;
}
putnext(q, mp); /* In case anyone else has to see it */
return;
case M_FLUSH:
/*
* Flush everything we haven't looked at yet.
*/
if ((tp->t_state & TS_ISPTSTTY) && (*mp->b_rptr & FLUSHBAND))
flushband(q, *(mp->b_rptr + 1), FLUSHDATA);
else
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;
tp->t_rocol = 0;
if (tp->t_state & TS_MEUC) { /* EUC multi-byte */
ASSERT(tp->t_eucp_mp);
tp->t_eucp = tp->t_eucp_mp->b_rptr;
}
putnext(q, mp); /* pass it on */
/*
* Relieve input flow control
*/
if ((tp->t_modes.c_iflag & IXOFF) &&
(tp->t_state & TS_TBLOCK) &&
!(tp->t_state & TS_IFBLOCK) && q->q_count <= TTXOLO) {
tp->t_state &= ~TS_TBLOCK;
(void) putnextctl(wrq, M_STARTI);
DEBUG1(("M_STARTI down\n"));
}
return;
case M_DATA:
break;
}
(void) drv_setparm(SYSRAWC, msgdsize(mp));
/*
* 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) &&
!(tp->t_state & TS_IFBLOCK) && q->q_count <= TTXOLO) {
tp->t_state &= ~TS_TBLOCK;
(void) putnextctl(wrq, M_STARTI);
DEBUG1(("M_STARTI down\n"));
}
/*
* 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_NOCANON) {
(void) putq(q, mp);
return;
}
bp = mp;
do {
readp = bp->b_rptr;
writep = 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)
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 NUL
* disables it, so if this character
* is NUL, 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 == _POSIX_VDISABLE) {
if (tp->t_modes.c_iflag & IXON &&
tp->t_state & TS_TTSTOP &&
tp->t_modes.c_lflag & IEXTEN &&
tp->t_modes.c_iflag & IXANY) {
tp->t_state &=
~(TS_TTSTOP|TS_OFBLOCK);
(void) putnextctl(wrq, 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_lflag &
IEXTEN &&
tp->t_modes.c_iflag &
IXANY)) {
tp->t_state &=
~(TS_TTSTOP |
TS_OFBLOCK);
(void) putnextctl(wrq,
M_START);
}
} else {
if (c ==
tp->t_modes.c_cc[VSTOP]) {
tp->t_state |=
TS_TTSTOP;
(void) putnextctl(wrq,
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
* 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]) {
ldterm_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]) {
ldterm_dosig(q, SIGINT, c,
M_PCSIG, FLUSHRW);
continue;
}
if (c == tp->t_modes.c_cc[VQUIT]) {
ldterm_dosig(q, SIGQUIT, c,
M_PCSIG, FLUSHRW);
continue;
}
if (c == tp->t_modes.c_cc[VSWTCH]) {
/*
* Ancient SXT support; discard
* character without action.
*/
continue;
}
if (c == tp->t_modes.c_cc[VSUSP]) {
ldterm_dosig(q, SIGTSTP, c,
M_PCSIG, FLUSHRW);
continue;
}
if ((tp->t_modes.c_lflag & IEXTEN) &&
(c == tp->t_modes.c_cc[VDSUSP])) {
ldterm_dosig(q, SIGTSTP, c,
M_SIG, 0);
continue;
}
/*
* Consumers do not expect the ^T to be
* echoed out when we generate a
* VSTATUS.
*/
if (c == tp->t_modes.c_cc[VSTATUS]) {
ldterm_dosig(q, SIGINFO, '\0',
M_PCSIG, FLUSHRW);
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)
*writep++ = *readp++ & 0177;
}
tp->t_modes.c_lflag &= ~FLUSHO;
}
} while ((bp = bp->b_cont) != NULL); /* next block, if any */
/*
* Queue the message for service procedure if the
* queue is not empty or canputnext() fails or
* tp->t_state & TS_RESCAN is true.
*/
if (q->q_first != NULL || !bcanputnext(q, mp->b_band) ||
(tp->t_state & TS_RESCAN))
(void) putq(q, mp);
else
(void) ldtermrmsg(q, mp);
/*
* Flow control: send "stop input" message if our queue is
* approaching its high-water mark. The message will be
* dropped on the floor in the service procedure, if we
* cannot ship it up and we have had it upto our neck!
*
* 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) {
mutex_enter(QLOCK(nextq));
nextq->q_flag |= QWANTW;
mutex_exit(QLOCK(nextq));
tp->t_state |= TS_TBLOCK;
(void) putnextctl(wrq, M_STOPI);
DEBUG1(("M_STOPI down\n"));
}
}
/*
* Line discipline input server processing. Erase/kill and escape
* ('\') processing, gathering into messages, upper/lower case input
* mapping.
*/
static void
ldtermrsrv(queue_t *q)
{
ldtermstd_state_t *tp;
mblk_t *mp;
tp = (ldtermstd_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) {
DEBUG5(("RESCAN WAS SET; put back in q\n"));
if (tp->t_msglen != 0)
(void) putbq(q, tp->t_message);
else
freemsg(tp->t_message);
tp->t_message = NULL;
tp->t_endmsg = NULL;
tp->t_msglen = 0;
}
if (tp->t_state & TS_MEUC) {
ASSERT(tp->t_eucp_mp);
tp->t_eucp = tp->t_eucp_mp->b_rptr;
tp->t_codeset = 0;
tp->t_eucleft = 0;
}
tp->t_state &= ~TS_RESCAN;
}
while ((mp = getq(q)) != NULL) {
if (!ldtermrmsg(q, mp))
break;
}
/*
* 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) &&
!(tp->t_state & TS_IFBLOCK) && q->q_count <= TTXOLO) {
tp->t_state &= ~TS_TBLOCK;
(void) putctl(WR(q), M_STARTI);
}
}
/*
* This routine is called from both ldtermrput and ldtermrsrv to
* do the actual work of dealing with mp. Return 1 on sucesss and
* 0 on failure.
*/
static int
ldtermrmsg(queue_t *q, mblk_t *mp)
{
unsigned char c;
int dofree;
int status = 1;
size_t ebsize;
mblk_t *bp;
mblk_t *bpt;
ldtermstd_state_t *tp;
bpt = NULL;
tp = (ldtermstd_state_t *)q->q_ptr;
if (mp->b_datap->db_type <= QPCTL && !bcanputnext(q, mp->b_band)) {
/*
* Stream head is flow controlled. If echo is
* turned on, flush the read side or send a
* bell down the line to stop input and
* process the current message.
* Otherwise(putbq) the user will not see any
* response to to the typed input. Typically
* happens if there is no reader process.
* Note that you will loose the data in this
* case if the data is coming too fast. There
* is an assumption here that if ECHO is
* turned on its some user typing the data on
* a terminal and its not network.
*/
if (tp->t_modes.c_lflag & ECHO) {
if ((tp->t_modes.c_iflag & IMAXBEL) &&
(tp->t_modes.c_lflag & ICANON)) {
freemsg(mp);
if (canputnext(WR(q)))
ldterm_outchar(CTRL('g'), WR(q), 4, tp);
status = 0;
goto echo;
} else {
(void) putctl1(q, M_FLUSH, FLUSHR);
}
} else {
(void) putbq(q, mp);
status = 0;
goto out; /* read side is blocked */
}
}
switch (mp->b_datap->db_type) {
default:
putnext(q, mp); /* pass it on */
goto out;
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;
/*
* XXX should we set read request
* tp->t_rd_request to NULL?
*/
tp->t_rocount = 0; /* if it hasn't been typed */
tp->t_rocol = 0; /* it hasn't been echoed :-) */
if (tp->t_state & TS_MEUC) {
ASSERT(tp->t_eucp_mp);
tp->t_eucp = tp->t_eucp_mp->b_rptr;
}
/*
* Restart output, since it's probably got
* nowhere to go anyway, and we're probably
* not going to see another ^Q for a while.
*/
if (tp->t_state & TS_TTSTOP) {
tp->t_state &= ~(TS_TTSTOP|TS_OFBLOCK);
(void) putnextctl(WR(q), M_START);
}
/*
* This message 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) putnextctl1(q, M_FLUSH, FLUSHW);
/*
* This message 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, FLUSHR);
/*
* Now that that's done, we send a SIGCONT
* upstream, followed by the M_HANGUP.
*/
/* (void) putnextctl1(q, M_PCSIG, SIGCONT); */
putnext(q, mp);
goto out;
case M_IOCACK:
/*
* Augment whatever information the driver is
* returning with the information we supply.
*/
ldterm_ioctl_reply(q, mp);
goto out;
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_NOCANON) {
putnext(q, mp);
goto out;
}
bp = mp;
if ((bpt = newmsg(tp)) != NULL) {
mblk_t *bcont;
do {
ASSERT(bp->b_wptr >= bp->b_rptr);
ebsize = bp->b_wptr - bp->b_rptr;
if (ebsize > EBSIZE)
ebsize = EBSIZE;
bcont = bp->b_cont;
if (CANON_MODE) {
/*
* By default, free the message once processed
*/
dofree = 1;
/*
* update sysinfo canch
* character. The value of
* canch may vary as compared
* to character tty
* implementation.
*/
while (bp->b_rptr < bp->b_wptr) {
c = *bp->b_rptr++;
if ((bpt = ldterm_docanon(c,
bpt, ebsize, q, tp, &dofree)) ==
NULL)
break;
}
/*
* Release this block or put back on queue.
*/
if (dofree)
freeb(bp);
else {
(void) putbq(q, bp);
break;
}
} else
bpt = ldterm_dononcanon(bp, bpt, ebsize, q, tp);
if (bpt == NULL) {
cmn_err(CE_WARN,
"ldtermrsrv: out of blocks");
freemsg(bcont);
break;
}
} while ((bp = bcont) != NULL);
}
echo:
/*
* Send whatever we echoed downstream.
*/
if (tp->t_echomp != NULL) {
if (canputnext(WR(q)))
putnext(WR(q), tp->t_echomp);
else
freemsg(tp->t_echomp);
tp->t_echomp = NULL;
}
out:
return (status);
}
/*
* 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 *
ldterm_docanon(uchar_t c, mblk_t *bpt, size_t ebsize, queue_t *q,
ldtermstd_state_t *tp, int *dofreep)
{
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 NUL disables it, so if this
* character is NUL, it should not be compared with any of
* the special characters.
*/
if (c == _POSIX_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)
ldterm_outstring((unsigned char *)"^\b", 2, wrq,
ebsize, tp);
tp->t_state |= TS_SLNCH;
goto out;
}
/*
* Check for the editing character. If the display width of
* the last byte at the canonical buffer is not one and also
* smaller than or equal to UNKNOWN_WIDTH, the character at
* the end of the buffer is a multi-byte and/or multi-column
* character.
*/
if (c == tp->t_modes.c_cc[VERASE] || c == tp->t_modes.c_cc[VERASE2]) {
if (tp->t_state & TS_QUOT) {
/*
* Get rid of the backslash, and put the
* erase character in its place.
*/
ldterm_erase(wrq, ebsize, tp);
bpt = tp->t_endmsg;
goto escaped;
} else {
if ((tp->t_state & TS_MEUC) && tp->t_msglen &&
(*(tp->t_eucp - 1) != 1 &&
*(tp->t_eucp - 1) <= UNKNOWN_WIDTH))
ldterm_csi_erase(wrq, ebsize, tp);
else
ldterm_erase(wrq, ebsize, tp);
bpt = tp->t_endmsg;
goto out;
}
}
if ((tp->t_modes.c_lflag & IEXTEN) && c == tp->t_modes.c_cc[VWERASE]) {
/*
* Do "ASCII word" or "multibyte character token/chunk" erase.
*/
if (tp->t_state & TS_MEUC)
ldterm_csi_werase(wrq, ebsize, tp);
else
ldterm_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.
*/
ldterm_erase(wrq, ebsize, tp);
bpt = tp->t_endmsg;
goto escaped;
} else {
ldterm_kill(wrq, ebsize, tp);
bpt = tp->t_endmsg;
goto out;
}
}
if ((tp->t_modes.c_lflag & IEXTEN) && c == tp->t_modes.c_cc[VREPRINT]) {
ldterm_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.
*/
ldterm_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') {
ldterm_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 & IEXTEN) &&
(tp->t_modes.c_lflag & ECHO)) {
i = ldterm_echo(c, wrq, ebsize, tp);
while (i > 0) {
ldterm_outchar('\b', wrq, ebsize, tp);
i--;
}
}
bpt->b_datap->db_type = M_DATA;
ldterm_msg_upstream(q, tp);
if (!canputnext(q)) {
bpt = NULL;
*dofreep = 0;
} else {
bpt = newmsg(tp);
*dofreep = 1;
}
goto out;
}
}
escaped:
/*
* First, make sure we can fit one WHOLE multi-byte char in the
* buffer. This is one place where we have overhead even if
* not in multi-byte mode; the overhead is subtracting
* tp->t_maxeuc from MAX_CANON before checking.
*
* Allows MAX_CANON bytes in the buffer before throwing awaying
* the the overflow of characters.
*/
if ((tp->t_msglen > ((_TTY_BUFSIZ + 1) - (int)tp->t_maxeuc)) &&
!((tp->t_state & TS_MEUC) && tp->t_eucleft)) {
/*
* Byte will cause line to overflow, or the next EUC
* won't fit: Ring the bell or discard all input, and
* don't save the byte away.
*/
if (tp->t_modes.c_iflag & IMAXBEL) {
if (canputnext(wrq))
ldterm_outchar(CTRL('g'), wrq, ebsize, tp);
goto out;
} else {
/*
* MAX_CANON processing. free everything in
* the current line and start with the
* current character as the first character.
*/
DEBUG7(("ldterm_docanon: MAX_CANON processing\n"));
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 type */
tp->t_rocol = 0; /* it hasn't been echoed :-) */
if (tp->t_state & TS_MEUC) {
ASSERT(tp->t_eucp_mp);
tp->t_eucp = tp->t_eucp_mp->b_rptr;
}
tp->t_state &= ~TS_SLNCH;
bpt = newmsg(tp);
}
}
/*
* 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++; /* message length in BYTES */
/*
* In multi-byte mode, we have to keep track of where we are.
* The first bytes of multi-byte chars get the full count for the
* whole character. We don't do any column calculations
* here, but we need the information for when we do. We could
* come across cases where we are getting garbage on the
* line, but we're in multi-byte mode. In that case, we may
* see ASCII controls come in the middle of what should have been a
* multi-byte character. Call ldterm_eucwarn...eventually, a
* warning message will be printed about it.
*/
if (tp->t_state & TS_MEUC) {
if (tp->t_eucleft) { /* if in a multi-byte char already */
--tp->t_eucleft;
*tp->t_eucp++ = 0; /* is a subsequent byte */
if (c < (uchar_t)0x20)
ldterm_eucwarn(tp);
} else { /* is the first byte of a multi-byte, or is ASCII */
if (ISASCII(c)) {
*tp->t_eucp++ =
tp->t_csmethods.ldterm_dispwidth(c,
(void *)tp, tp->t_modes.c_lflag & ECHOCTL);
tp->t_codeset = 0;
} else {
*tp->t_eucp++ =
tp->t_csmethods.ldterm_dispwidth(c,
(void *)tp, tp->t_modes.c_lflag & ECHOCTL);
tp->t_eucleft =
tp->t_csmethods.ldterm_memwidth(c,
(void *)tp) - 1;
tp->t_codeset = ldterm_codeset(
tp->t_csdata.codeset_type, c);
}
}
}
/*
* AT&T is concerned about the following but we aren't since
* we have already shipped code that works.
*
* EOL2/XCASE should be conditioned with IEXTEN to be truly
* POSIX conformant. This is going to cause problems for
* pre-SVR4.0 programs that don't know about IEXTEN. Hence
* EOL2/IEXTEN is not conditioned with IEXTEN.
*/
if (!(tp->t_state & TS_SLNCH) &&
(c == '\n' || (c != '\0' && (c == tp->t_modes.c_cc[VEOL] ||
(c == tp->t_modes.c_cc[VEOL2]))))) {
/*
* || ((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;
ldterm_msg_upstream(q, tp);
if (tp->t_state & TS_MEUC) {
ASSERT(tp->t_eucp_mp);
tp->t_eucp = tp->t_eucp_mp->b_rptr;
}
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 the current character is a single byte and single
* column character and it is the backslash character and
* IEXTEN, then the state will have TS_QUOT.
*/
if ((c == '\\') && (tp->t_modes.c_lflag & IEXTEN) &&
(!(tp->t_state & TS_MEUC) ||
((tp->t_state & TS_MEUC) && (!tp->t_eucleft))))
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)
ldterm_outchar('/', wrq, ebsize, tp);
}
if (tp->t_modes.c_lflag & ECHO)
(void) ldterm_echo(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))
ldterm_outchar(c, wrq, ebsize, tp);
}
out:
return (bpt);
}
static int
ldterm_unget(ldtermstd_state_t *tp)
{
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);
}
static void
ldterm_trim(ldtermstd_state_t *tp)
{
mblk_t *bpt;
mblk_t *bp;
ASSERT(tp->t_endmsg);
bpt = tp->t_endmsg;
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) {
ASSERT(bp->b_cont);
bp = bp->b_cont;
}
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. Most of this
* can't be applied to multi-byte processing. We do our own thing
* for that... See the "ldterm_eucerase" routine. We never call
* ldterm_rubout on a multi-byte or multi-column character.
*/
static void
ldterm_rubout(uchar_t c, queue_t *q, size_t ebsize, ldtermstd_state_t *tp)
{
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.
*/
ldterm_reprint(q, ebsize, tp);
return;
} else {
/*
* XXX what about escaped characters?
*/
switch (typetab[c]) {
case ORDINARY:
if ((tp->t_modes.c_lflag & XCASE) &&
omaptab[c])
ldterm_outstring(RUBOUT1, 3, q, ebsize,
tp);
ldterm_outstring(RUBOUT1, 3, q, ebsize, tp);
break;
case VTAB:
case BACKSPACE:
case CONTROL:
case RETURN:
case NEWLINE:
if ((tp->t_modes.c_lflag & ECHOCTL) &&
(tp->t_modes.c_lflag & IEXTEN))
ldterm_outstring(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.
*/
ldterm_reprint(q, ebsize, tp);
return;
}
tabcols = ldterm_tabcols(tp);
while (--tabcols >= 0)
ldterm_outchar('\b', q, ebsize, tp);
break;
}
}
} else if ((tp->t_modes.c_lflag & ECHOPRT) &&
(tp->t_modes.c_lflag & IEXTEN)) {
/*
* "Printing rubout"; echo it between \ and /.
*/
if (!(tp->t_state & TS_ERASE)) {
ldterm_outchar('\\', q, ebsize, tp);
tp->t_state |= TS_ERASE;
}
(void) ldterm_echo(c, q, ebsize, tp);
} else
(void) ldterm_echo(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
ldterm_tabcols(ldtermstd_state_t *tp)
{
int col;
int i;
mblk_t *bp;
unsigned char *readp, *endp;
unsigned char c;
uchar_t *startp;
char errflg;
uchar_t u8[LDTERM_CS_MAX_BYTE_LENGTH];
col = tp->t_rocol;
/*
* If we're doing multi-byte stuff, zip through the list of
* widths to figure out where we are (we've kept track in most
* cases).
*/
if (tp->t_state & TS_MEUC) {
ASSERT(tp->t_eucp_mp);
bp = tp->t_message;
startp = bp->b_datap->db_base;
readp = tp->t_eucp_mp->b_rptr;
endp = tp->t_eucp;
errflg = (char)0;
while (readp < endp) {
switch (*readp) {
case EUC_TWIDTH: /* it's a tab */
col |= 07; /* bump up */
col++;
break;
case EUC_BSWIDTH: /* backspace */
if (col)
col--;
break;
case EUC_NLWIDTH: /* newline */
if (tp->t_modes.c_oflag & ONLRET)
col = 0;
break;
case EUC_CRWIDTH: /* return */
col = 0;
break;
case UNKNOWN_WIDTH: /* UTF-8 unknown width */
if (tp->t_csdata.codeset_type !=
LDTERM_CS_TYPE_UTF8 || errflg) {
*readp = 1;
col++;
break;
}
/*
* Collect the current UTF-8 character bytes
* from (possibly multiple) data buffers so
* that we can figure out the display width.
*/
u8[0] = *startp;
for (i = 1; (i < LDTERM_CS_MAX_BYTE_LENGTH) &&
(*(readp + i) == 0); i++) {
startp++;
if (startp >= bp->b_datap->db_lim) {
if (bp->b_cont) {
bp = bp->b_cont;
startp =
bp->b_datap->
db_base;
} else {
*readp = 1;
col++;
break;
}
}
u8[i] = *startp;
}
/* tp->t_eucp_mp contains wrong info?? */
if (*readp == 1)
break;
*readp = ldterm_utf8_width(u8, i);
col += *readp;
readp += (i - 1);
break;
default:
col += *readp;
break;
}
++readp;
++startp;
if (startp >= bp->b_datap->db_lim) {
if (bp->b_cont) {
bp = bp->b_cont;
startp = bp->b_datap->db_base;
} else {
/*
* This will happen only if
* tp->t_eucp_mp contains wrong
* display width info.
*/
errflg = (char)1;
startp--;
}
}
}
goto eucout; /* finished! */
}
bp = tp->t_message;
do {
readp = bp->b_rptr;
while (readp < bp->b_wptr) {
c = *readp++;
if ((tp->t_modes.c_lflag & ECHOCTL) &&
(tp->t_modes.c_lflag & IEXTEN)) {
if (c <= 037 && c != '\t' && c != '\n' ||
c == 0177) {
col += 2;
continue;
}
}
/*
* Column position calculated here.
*/
switch (typetab[c]) {
/*
* Ordinary characters; advance by
* one.
*/
case ORDINARY:
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.
*/
eucout:
col = tp->t_col - col;
if (col > 8)
col = 8; /* overflow screw */
return (col);
}
/*
* Erase a single character; We ONLY ONLY deal with ASCII or
* single-column single-byte codeset character. For multi-byte characters,
* see "ldterm_csi_erase".
*/
static void
ldterm_erase(queue_t *q, size_t ebsize, ldtermstd_state_t *tp)
{
int c;
if ((c = ldterm_unget(tp)) != -1) {
ldterm_rubout((unsigned char) c, q, ebsize, tp);
ldterm_trim(tp);
if (tp->t_state & TS_MEUC)
--tp->t_eucp;
}
}
/*
* Erase an entire word, single-byte EUC only please.
*/
static void
ldterm_werase(queue_t *q, size_t ebsize, ldtermstd_state_t *tp)
{
int c;
/*
* Erase trailing white space, if any.
*/
while ((c = ldterm_unget(tp)) == ' ' || c == '\t') {
ldterm_rubout((unsigned char) c, q, ebsize, tp);
ldterm_trim(tp);
}
/*
* Erase non-white-space characters, if any.
*/
while (c != -1 && c != ' ' && c != '\t') {
ldterm_rubout((unsigned char) c, q, ebsize, tp);
ldterm_trim(tp);
c = ldterm_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++;
}
}
/*
* ldterm_csi_werase - This is multi-byte equivalent of "word erase".
* "Word erase" only makes sense in languages which space between words,
* and it's presumptuous for us to attempt "word erase" when we don't
* know anything about what's really going on. It makes no sense for
* many languages, as the criteria for defining words and tokens may
* be completely different.
*
* In the TS_MEUC case (which is how we got here), we define a token to
* be space- or tab-delimited, and erase one of them. It helps to
* have this for command lines, but it's otherwise useless for text
* editing applications; you need more sophistication than we can
* provide here.
*/
static void
ldterm_csi_werase(queue_t *q, size_t ebsize, ldtermstd_state_t *tp)
{
int c, i;
int len;
uchar_t *ip;
uchar_t u8[LDTERM_CS_MAX_BYTE_LENGTH];
uchar_t u8_2[LDTERM_CS_MAX_BYTE_LENGTH];
/*
* ip points to the width of the actual bytes. t_eucp points
* one byte beyond, where the next thing will be inserted.
*/
ip = tp->t_eucp - 1;
/*
* Erase trailing white space, if any.
*/
while ((c = ldterm_unget(tp)) == ' ' || c == '\t') {
tp->t_eucp--;
ldterm_rubout((unsigned char) c, q, ebsize, tp);
ldterm_trim(tp);
--ip;
}
/*
* Erase non-white-space characters, if any. The outer loop
* bops through each byte in the buffer. Multi-byte is removed, as
* is ASCII, one byte at a time. The inner loop (for) is only
* executed for first bytes of multi-byte. The inner loop erases
* the number of columns required for the multi-byte char. We check
* for ASCII first, and ldterm_rubout knows about ASCII.
*/
len = 0;
while (c != -1 && c != ' ' && c != '\t') {
tp->t_eucp--;
if (len < LDTERM_CS_MAX_BYTE_LENGTH) {
u8[len++] = (uchar_t)c;
}
/*
* Unlike EUC, except the leading byte, some bytes of
* a non-EUC multi-byte characters are in the ASCII code
* range, esp., 0x41 ~ 0x7a. Thus, we cannot simply check
* ISASCII().
* Checking the (*ip == 1 || *ip == 2 || *ip > UNKNOWN_WIDTH)
* will ensure that it is a single byte character (even though
* it is on display width not byte length) and can be further
* checked whether it is an ASCII character or not.
*
* When ECHOCTL is on and 'c' is an ASCII control character,
* *ip == 2 happens.
*/
if ((*ip == 1 || *ip == 2 || *ip > UNKNOWN_WIDTH) &&
ISASCII(c)) {
ldterm_rubout((unsigned char) c, q, ebsize, tp);
len = 0;
} else if (*ip) {
if (*ip == UNKNOWN_WIDTH) {
if (tp->t_csdata.codeset_type ==
LDTERM_CS_TYPE_UTF8) {
for (i = 0; i < len; i++)
u8_2[i] = u8[len - i - 1];
*ip = ldterm_utf8_width(u8_2, len);
} else {
*ip = 1;
}
}
/*
* erase for number of columns required for
* this multi-byte character. Hopefully, matches
* ldterm_dispwidth!
*/
for (i = 0; i < (int)*ip; i++)
ldterm_rubout(' ', q, ebsize, tp);
len = 0;
}
ldterm_trim(tp);
--ip;
c = ldterm_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 an entire line, erasing each character one-by-one (if ECHOKE
* is set) or just echoing the kill character, followed by a newline
* (if ECHOK is set). Multi-byte processing is included here.
*/
static void
ldterm_kill(queue_t *q, size_t ebsize, ldtermstd_state_t *tp)
{
int c, i;
int len;
uchar_t *ip;
uchar_t u8[LDTERM_CS_MAX_BYTE_LENGTH];
uchar_t u8_2[LDTERM_CS_MAX_BYTE_LENGTH];
if ((tp->t_modes.c_lflag & ECHOKE) &&
(tp->t_modes.c_lflag & IEXTEN) &&
(tp->t_msglen == tp->t_rocount)) {
if (tp->t_state & TS_MEUC) {
ip = tp->t_eucp - 1;
/*
* This loop similar to "ldterm_csi_werase" above.
*/
len = 0;
while ((c = ldterm_unget(tp)) != (-1)) {
tp->t_eucp--;
if (len < LDTERM_CS_MAX_BYTE_LENGTH) {
u8[len++] = (uchar_t)c;
}
if ((*ip == 1 || *ip == 2 ||
*ip > UNKNOWN_WIDTH) && ISASCII(c)) {
ldterm_rubout((unsigned char) c, q,
ebsize, tp);
len = 0;
} else if (*ip) {
if (*ip == UNKNOWN_WIDTH) {
if (tp->t_csdata.codeset_type
== LDTERM_CS_TYPE_UTF8) {
for (i = 0; i < len;
i++)
u8_2[i] =
u8[len-i-1];
*ip = ldterm_utf8_width(
u8_2, len);
} else {
*ip = 1;
}
}
for (i = 0; i < (int)*ip; i++)
ldterm_rubout(' ', q, ebsize,
tp);
len = 0;
}
ldterm_trim(tp);
--ip;
}
} else {
while ((c = ldterm_unget(tp)) != -1) {
ldterm_rubout((unsigned char) c, q, ebsize, tp);
ldterm_trim(tp);
}
}
} else {
(void) ldterm_echo(tp->t_modes.c_cc[VKILL], q, ebsize, tp);
if (tp->t_modes.c_lflag & ECHOK)
(void) ldterm_echo('\n', q, ebsize, tp);
while (ldterm_unget(tp) != -1) {
if (tp->t_state & TS_MEUC)
--tp->t_eucp;
ldterm_trim(tp);
}
tp->t_rocount = 0;
if (tp->t_state & TS_MEUC)
tp->t_eucp = tp->t_eucp_mp->b_rptr;
}
tp->t_state &= ~(TS_QUOT|TS_ERASE|TS_SLNCH);
}
/*
* Reprint the current input line. We assume c_cc has already been
* checked. XXX just the current line, not the whole queue? What
* about DEFECHO mode?
*/
static void