PROBLEM SOLVER
Unraveling the mysteries of System V's terminal interface
by Bob Toxen
As anyone who has dealt with System V (or System III) knows, the System V
terminal interface has a completely different means for getting and setting
terminal modes than does Version 7 or BSD 4.2. The names of the system calls,
the content of the manual page entries -- even the sections the manual entries
are in -- have been changed. While the new system calls take some time to
learn, they offer more power and versatility than Version 7's I/O control and
are more systematic and easier to use than Berkeley's 4.2 distribution because
they were built from scratch rather than on top of Version 7, which in turn
was built on top of Version 6.
The system call for getting and setting terminal modes is called
ioctl() and takes three arguments. The first is an integer
specifying the file descriptor; the second is the request,
an integer that specifies what operation is to be performed; the third
argument is either a pointer to a termio structure or an
integer, depending on the request.
The first group of requests, called primary ioctls,
expect the third argument to be a pointer to the termio
structure. This structure and some useful constants are defined in
<termio.h>, which should be included in your program. (This
structure will be discussed later.)
There are basically two requests. The first, TCGETA, reads
the current parameters into the user-supplied structure pointed to by the
third argument, which I will call arg. The second,
TCSETA, immediately sets the terminal parameters according to
the values stored in the user-supplied structure specified by arg.
These are similar to getty and setty in
functionality.
Additionally, the TCSETAW request is identical to
TCSETA except that it waits for the output buffer to drain
before taking effect. Typically, it should be used if output parameters are
being changed. Lastly, there is the TCSETAF request that
waits for the output to drain before flushing the input queue (throwing away
typed characters that have not been read by any program yet) and changing the
parameters.
There are three secondary ioctls that are useful for
communications; they all take an integer arg. The first,
TCSBRK, waits for the output to drain (be sent to the
terminal). Then, if arg is 0, it will send a break signal to the
device (zero bits for 0.25 seconds). This feature is used by
cu and uucp to cause remote computers to
change baud rates. In cu, one generates a break by entering
the sequence:
~%b
or
~%break
at the beginning of a line, followed by a newline. This is not documented in
CU(1).
This second secondary request is TCXONC, which is used to
start and stop (suspend) output or input transmission. This is useful for
implementing protocols other than X-on/X-off. If
arg is 0, then output is suspended; if it is 1, then suspended output
is restarted. If arg is 2, suspend input (send a
XOFF to device); if 3, restart suspended input (send a
XON to device). The fact that arg may be 2 or 3 is
also undocumented.
The last secondary request, TCFLSH, may be used to flush the
input or output queues. If arg is 0, the input queue is flushed; if
it is 1, the output queue is flushed; and if it is 2, both queues are flushed.
In this regard, arg is similar to the second argument to
open().
The termio structure is the user-supplied structure whose
address is passed to the primary ioctls as arg. It
is defined in <termio.h> and an example appears in Figure 1.
#define NCC 8
struct termio {
unsigned short c_iflag; /* input modes */
unsigned short c_oflag; /* output modes */
unsigned short c_cflag; /* control modes */
unsigned short c_lflag; /* line discipline modes */
char c_line; /* line discipline */
unsigned char c_cc[NCC]; /* control chars */
};
Figure 1 -- The termio structure defined
in <termio.h>.
The first four members are each 16-bit unsigned integers. Each is logically
split up into bit-fields, most of which are a single bit long. If the bit is
on, the feature is enabled; otherwise it is disabled. Some of these features
interact with others. These fields may be easily accessed with defined
constants supplied in the <termio.h> include file.
INPUT MODES
The c_iflag member is for input modes. The first of these is
IGNBRK. When this field is set, the BREAK
condition is ignored. A BREAK may be generated by pressing
the BREAK key on the terminal. It is commonly used to switch
the baud rate when initially connecting to a computer since
BREAK is not affected by differing baud rates.
BREAKs are also generated when Alpha Centauri preempts your
data with cosmic rays. The latter method is the reason for the
IGNBRK feature.
On the other hand, you may want to use the BREAK key to
interrupt programs instead of the more popular CTRL-C or
INTerrupt keys. The BRKINT feature does
this. It causes BREAK to generate an interrupt signal and
flush the input and output queues (throw away all unread typed characters and
output characters that have not yet been printed) -- just as CTRL-C or
INTerrupt can.
IGNPAR causes the system to throw away characters with parity
errors. It will also cause characters with framing errors other than
NULs to be ignored. A NUL with a framing
error is interpreted as a BREAK. A character with a framing
error is one without a stop bit and is generated either by the
BREAK key or those nice folks from Alpha Centauri.
The PARMRK is not usually used. It will cause any characters
with framing errors to be preceded by both a byte with all eight bits on and a
NUL byte. This is only useful if you are using advanced
error recovery techniques. If PARMRK is on, a legitimate
byte made up of 8 ones on will be entered in the input queue twice. Thus when
such a byte is read and the following byte is the same, you can be assured
you have received legitimate data. But, if the second character is
NUL, a character with a parity or framing error follows.
INPCK will cause all the parity handling discussed for
IGNPAR and PARMRK. if
INPCK is off, a NUL byte is returned for
characters with parity or framing errors. The ISTRIP flag
will strip the eighth bit (zero it).
The INLCR flag causes typed newlines (linefeeds) to be
changed to carriage returns. IGNCR causes carriage returns
to be ignored and is useful for terminals that generate both a carriage return
and a newline when the RETURN key is pressed. The
ICRNL flag causes typed carriage returns to be converted to
newlines and is usually enabled since most terminals have a big
RETURN key and a small linefeed key.
Readers with TTY33s can contact the author about the meaning of
IUCLC. (Hint: UC means
Upper Case.)
The IXON flag allows you to use a CTRL-S
(X-off) or CTRL-Q (X-on). CTRL-S allows you
to stop output so you can read it before it flies off the screen; CTRL-Q
allows you to continue output. In case you can't remember that CTRL-Q allows
you to continue output, you can use the IXANY flag to allow
any character (except a CTRL-S) to continue output. Whatever character is
entered to continue output is supplied to the program reading the input queue
-- unless it is a CTRL-Q.
Lastly, there is the IXOFF flag which causes the
system to send a CTRL-S to the input device if the system's input
queue is getting full, and a CTRL-Q after space has been made free in the
input queue. The IXOFF flag is primarily used when the input
device is not a terminal but another computer.
That's it for the input modes! The rest of it is not so bad.
OUTPUT MODES
The c_oflag member is for output modes. The first flag,
OPOST, determines whether or not output processing is done.
That is, whether output characters destined for the device should be diddled
with, delayed to allow for carriage movement, or manipulated in any other way.
The OLCUC and OCRNL flags are the
counterparts of the input flags with similar names. ONLCR
maps newlines to return newline pairs on output. This flag is on for most
CRTs and printers. The ONOCR flag prevents a carriage return
from being sent if the cursor (or printer mechanism) is already at column 0.
This is useful for some terminals and printers and can help speed transmission
at low speeds.
The ONLRET flag means that the terminal will perform a
carriage return as well as a linefeed upon receipt of a newline (linefeed).
This affects delays and future expansion of tabs. OFILL
causes fill characters to be transmitted to cause delays for carriage movement
such as returns, linefeeds, tabs, et al. This feature is not often used since
most terminal drivers actually delay transmission of further characters for
the prescribed time. OFILL is useful if output is filtered
through another computer whose buffering would destroy delay timings but not
fill characters. It may also be useful to simplify drivers that use
DMA or other techniques. The OFDEL causes
DELete characters (all seven bits ones) to be used as fill
characters (if OFILL is on) instead of NULs.
Some terminal devices or printers will immediately throw away
NULs but buffer DELs.
All of the rest of the fields in the c_oflag members select
the amount of delay to be used for various operations. These operations are:
newline (NL), carriage return (CR),
TAB (TAB), backspace (BS), vertical tab
(VT), and form feed (FF). The defines
ending DLY are used for masking delays for the respective
functions. The defines ending in digits select a style of delay; those ending
in zero mean no delay. The manual entry documents the actual timings for the
different styles of delays.
The c_cflag member is for control modes, mainly baud rate and
parity. The first define, CBAUD, is a mask for the bits that
select the baud rate. The entries starting with B are for the various baud
rates. Selecting a baud rate of B0 will break most connections and drop
DTR, hanging up modems. EXTA is commonly
used for a baud rate of 19200. EXTB is usually not defined.
CSIZE is a mask for the number of bits transmitted per
character. It is usually CS8. If CSTOPB is
on, two stop bits will be sent (to support good old TTY33 at 110 baud). This
flag is rarely used otherwise.
If PARENB is set, parity will be generated on output
characters and detected on input (though the IGNPAR,
PARMRK and INPCK flags can also determine
how input parity errors are handled). If PARENB is set then
if PARODD is set, then odd parity will be used; otherwise
parity will be even.
When HUPCL is set and all the processes (programs) that have
a terminal device open have closed their respective file descriptors for it
(perhaps by exiting), the data terminal ready (DTR) signal
will be dropped (made false). This will cause most correctly cabled modems to
hang up the line (hang up the phone). This feature is usually used if there
is actually a modem connected. Many microcomputers do not support this
feature, however.
If CLOCAL is on, the terminal driver won't require the
terminal to assert the DTR signal before allowing the
open() system call to complete. Otherwise
open() will hang until the DTR signal is
present (DTR is sometimes referred to as
the carrier). Of course, before you can do an
ioctl() system call to set CLOCAL, you must
do an open() to open the device file -- which will hang until
DTR is present, in which case CLOCAL is
unneeded. The O_NDELAY flag to open() may
be used to open the device without waiting for the carrier.
The c_lflag member is for the last set of features. The
first feature, ISIG, will enable the typing of certain
characters to generate the interrupt or quit signals.
The ICANON flag enables erase and kill processing and
accumulation of input data into lines. This means that input characters are
accumulated in an internal buffer. When a newline (linefeed character) is
received, this accumulated data, including the newline, is made available to
any program doing a read() system call. If
ICRNL is on, a RETURN entered at the
keyboard has the same effect as a linefeed.
It is immaterial whether the read() is invoked before or
after the newline (or any of the other characters) is received. The user may
specify that another character may also be used to terminate lines in the
same way that the newline does. This will be discussed later. If one types
the EOF character, usually a CTRL-D, then the characters are
also available for reading but the EOF character itself (the
CTRL-D) is thrown away.
If there are no accumulated characters when the CTRL-D is entered (because it
immediately follows a previous CTRL-D or newline character), the
read() returns a count of zero, i.e. no characters to be
read. This is usually interpreted as end of file. If the program
does another read() instead of exiting, this subsequent
read() will be treated as any other read of a terminal
device. That is, it will not return until the accumulation of characters is
completed with a new line or CTRL-D. This implementation is used by
vi for scrolling and by csh's
ignoreeof feature.
If ICANON is on and the erase character is entered, the last
character accumulated is sent off to Alpha Centauri. The erase character is
also thrown away. Likewise, if the line kill character is entered, all
characters accumulated are thrown away. The kill character is also thrown
away. The XCASE feature supports upper case-only terminals
and is of interest only to museums that have TTY33s.
If ECHO is set, characters will be echoed in the output as
they are received. If ECHOE (echo erase character, cleverly)
is set as well as ECHO and ICANON, the erase
character will be echoed as a backspace followed by a space followed by a
second backspace. On CRTs this has the effect of making the mistyped
character disappear -- a major departure from Version 7, where the same action
would merely position the cursor over the character.
Guess what ECHOK does? WRONG! The UNIX
Support Group implemented it to echo a newline after echoing the kill
character itself, which is frequently set to a control character. Maybe in
V.3... If ECHONL is set then when a kill character is typed,
the newline following it is echoed even if ECHO is turned
off.
If ICANON is not set, then we play by completely
different rules. Specifically, no erase or kill processing is one and a
newline (or EOF) character does not have to be entered in
order for a program to read the received characters. The program does,
however, have to wait for a set number of characters to be received or for a
set number of tenths of seconds after the read() call for it
to return. These two thresholds may be set by the user.
If the count
character threshold is set to one, the read() will return
after every character. If the count threshold is set to seven and the time
threshold is set to 13, the read() will return when seven
characters are received or after 13/10 (or 1.3) seconds has elapsed. Even if
1.3 seconds has elapsed, the read() will not return until at
least one character has been received unless the O_NDELAY
flag was supplied when the device file was opened, in which case the
read() will return with a count of zero.
Lastly, if NOFLSH is set, the accumulated input and output
characters will not be thrown away when an interrupt or quit signal is sent
from the terminal.
The line discipline may be specified with c_line. A line
discipline of zero, the only one supported, supplies ISIG,
ICANON, ECHO, etc. if these bits are set.
The c_cc[] member of the termio structure is
a character array that specifies the characters that are used for the erase
character, kill character, EOF character, etc. A "character"
of -1 disables the respective feature. There is a defined symbol,
NCC, that specifies the number of elements in the
c_cc[] member, currently eight.
The interrupt character c_cc[VINTR] defaults to
DELete. The character c_cc[VQUIT], which
terminates a program with a core dump, defaults to CTRL-\. For programs in
development, any character will suffice. The erase character
c_cc[VERASE] defaults to "#" or backspace,
depending on the port. The character c_cc[VEOF] specifies
the end of file and defaults to EOT (CTRL-D). The character
c_cc[VEOL] may be used to terminate lines and will default to
newline. The character c_cc[VEOL2] may be used to terminate
lines instead of newlines and will default to NUL.
The last two bytes are reserved, which is unfortunate since they should be
used for specifying the count and time thresholds if ICANON
is turned off. Instead, c_cc[VMIN] is used as the count
threshold and c_cc[VTIME] is used as the time threshold.
These are treated as 8-bit unsigned integers for this purpose. Unfortunately,
VMIN = VEOF and VTIME = VEOL.
This means that whenever ICANON is turned on, you will want
to set c_cc[VEOF] and c_cc[VEOL] to be the
EOF and EOL characters. When
ICANON is turned off, you will want to set
c_cc[VMIN] and c_cc[VTIME] for the
thresholds.
If you want to set the threshold counts with the stty program
in a standard implementation, you must specify the character whose ASCII value
is that of the number you want. In other words, specify CTRL-E for a count of
5. Specifying "5" will give you a count of 53. I learned this the hard way
and subsequently fixed the stty program and documentation for
that port.
Figures 2-7 summarize which mode bits and bytes are stored in each structure
member.
c_iflag
______________________________________
IGNBRK BRKINT IGNPAR
PARMRK INPCK ISTRIP
INLCR IGNCR ICRNL
IUCLC IXON IXANY
IXOFF
Figure 2 -- Input modes.
c_oflag
______________________________________
OPOST OLCUC ONLCR
OCRNL ONOCR ONLRET
OFILL OFDEL NLDLY
NL0 NL1 CRDLY
CR0 CR1 CR2
CR3 TABDLY TAB0
TAB1 TAB2 TAB3
BSDLY BS0 BS1
VTDLY VT0 VT1
FFDLY FF0 FF1
Figure 3 -- Output modes.
c_cflag
______________________________________
CBAUD B0 B50
B75 B110 B134
B150 B200 B300
B600 B1200 B1800
B2400 B4800 B9600
EXTA EXTB CSIZE
CS5 CS6 CS7
CS8 CSTOPB CREAD
PARENB PARODD HUPCL
CLOCAL
Figure 4 -- Control modes.
c_lflag
______________________________________
ISIG ICANON XCASE
ECHO ECHOE ECHOK
ECHONL NOFLSH
Figure 5 -- Line modes.
c_cc
______________________________________
VINTR VQUIT VERASE
VKILL VEOF VEOL
VEOL2 VMIN VTIME
Figure 6 -- Control characters.
Alphabetized modes
_________________________________________
Mode Type Mode Type
_________________________________________
B0 control FFDLY output
B110 control HUPCL control
B1200 control ICANON line
B134 control ICRNL input
B150 control IGNBRK input
B1800 control IGNCR input
B200 control IGNPAR input
B2400 control INLCR input
B300 control INPCK input
B4800 control ISIG line
B50 control ISTRIP input
B600 control IUCLC input
B75 control IXANY input
B9600 control IXOFF input
BRKINT input IXON input
BS0 output NL0 output
BS1 output NL1 output
BSDLY output NLDLY output
CBAUD control NOFLSH line
CLOCAL control OCRNL output
CR0 output OFDEL output
CR1 output OFILL output
CR2 output OLCUC output
CR3 output ONLCR output
CRDLY output ONLRET output
CREAD control ONOCR output
CS5 control OPOST output
CS6 control PARENB control
CS7 control PARMRK input
CS8 control PARODD control
CSIZE control TAB0 output
CSTOPB control TAB1 output
ECHO line TAB2 output
ECHOE line TAB3 output
ECHOK line TABDLY output
ECHONL line VT0 output
EXTA control VT1 output
EXTB control VTDLY output
FF0 output XCASE line
FF1 output
Figure 7 -- An alphabetic listing of the mode bits along
with the structure members they appear in.
So now that you know about all these modes, what do you do with them? When
you are logged into a System V computer, your modes will typically be like
those shown in Figure 8.
speed 9600 baud; line = 0; intr = ^c; quit = ^|;
erase = ^h; kill = DEL; eof = ^d; eol = ^`
-parenb -parodd cs8 -cstopb -hupcl cread -clocal
-ignbrk brkint ignpar -parmrk -inpck istrip
-inlcr -igncr icrnl -iuclc ixon -ixany -ixoff
isig icanon -xcase echo echoe echok -echonl -noflsh
opost -olcuc onlcr -ocrnl -onocr -onlret -ofill -ofdel
Figure 8 -- Typical system V modes.
speed 9600 baud; line = 0; intr = ^c; quit = ^|;
erase = ^h; kill = DEL; eof = ^a; eol = ^a
-parenb -parodd cs8 -cstopb -hupcl cread -clocal
-ignbrk brkint ignpar -parmrk -inpck istrip
-inlcr -igncr -icrnl -iuclc ixon -ixany -ixoff
isig -icanon -xcase echo echoe echok -echonl -noflsh
opost -olcuc -onlcr -ocrnl -onocr -onlret -ofill -ofdel
Figure 9 -- An example of Cbreak mode.
speed 9600 baud; line = 0; intr = ^c; quit = ^|;
erase = ^h; kill = DEL; eof = ^a; eol = ^a
-parenb -parodd cs8 -cstopb -hupcl cread -clocal
-ignbrk -brkint -ignpar -parmrk -inpck -istrip
-inlcr -igncr -icrnl -iuclc -ixon -ixany -ixoff
-isig -icanon -xcase echo echoe echok -echonl -noflsh
-opost -olcuc onlcr -ocrnl -onocr -onlret -ofill -ofdel
Figure 10 -- An example of raw mode.
___________________________________
Feature Cooked Cbreak Raw
___________________________________
eof ^d ^a ^a
eol ^` ^a ^a
___________________________________
brkint on on off
ignpar on on off
istrip on on off
___________________________________
icrnl on off off
ixon on on off
isig on on off
icanon on off off
___________________________________
opost on on off
onlcr on off on
Figure 11 -- Some of the differences between the three
"standard" terminal modes of operation.
This is known as "cooked" mode. Note that some features such as the kill
character, speed and parity can vary. A dash indicates that a feature is
turned off. A caret is shorthand for CTRL, as in ^C or CTRL-C. A ^| means
CTRL-\ and ^` means CTRL-@ (NUL).
The latter two are, technically, caused by a bug in stty's
algorithm that expands control characters to printable representations.
Speaking of stty bugs, if stty is invoked
without the -a flag, it will list those modes that are
different from "standard" cooked mode. If CREAD is turned
off, stty will list it as cread when it
should say -cread. When CREAD is turned
off, all read requests will return 0 -- giving the appearance of end of file
(lots of CTRL-Ds). This is usually the result of a programming error.
Cbreak mode looks like the example in Figure 9. Raw mode looks like the
example in Figure 10.
Since I do not like to torture people, Figure 11 details some of the
differences between the three "standard" terminal modes of operation.
It would be worthwhile to pause at this time and study the lists and table of
ioctl modes for cooked, Cbreak and raw modes. Some modes are
not operational when certain other modes have particular values. For example,
the value of onlcr is unimportant in raw mode because
opost, which must be on in order for onlcr
to have any effect, is off. Because of these "don't care" states, one
person's official System V raw mode may be slightly different from another's.
There may be other differences due to subtle errors. For example, in one
implementation of raw mode I've encountered, icrnl was on,
causing typed RETURN characters to be converted to newlines,
making it impossible to give an actual RETURN character to
the program. This made a difference in a program I know of, an Ethernet front
end, since being unable to send a RETURN to
vi, which was running on the remote system, limited my
operations.
An even more serious problem appeared in the same episode since
opost and onlcr were on, causing a
RETURN to precede each linefeed. Since each computer was
doing the expansion, each linefeed was preceded by two
RETURNs. This was not a problem except when
vi tried to do cursor optimizations by doing a linefeed
"without" a RETURN to quickly move the cursor to the middle
of the next line. However, the computer connected directly to the live CRT
still incorrectly preceded the linefeed with a RETURN
resulting incorrectly displayed text -- and much contemplation before the
cause was discovered.
Now that you thoroughly understand the theory of the System V terminal
interface, let's look at some examples. Suppose that you want a program to
turn off echoing of characters, say, to input the password to scramble SAC.
The program might look like the one in Figure 12.
#define STDIN 0
#include <termio.h>
struct termio u235;
char nsa[82];
main()
{
ioctl(STDIN, TCGETA, &u235);
u235.c_lflag &= ~ECHO;
ioctl(STDIN, TCSETA, &u235);
printf("Enter password:");
read(STDIN, nsa, sizeof nsa - 1);
u235.c_lflag |= ECHO;
ioctl(STDIN, TCSETA, &u235);
printf("\nYour password is %s", nsa);
}
Figure 12 -- An example program for turning off echoing
of characters.
Suppose that we want to input a password such that the program can't be
aborted with a signal (be careful if you try this). Also, as in the previous
example, we turn ECHO off. The program might look like the
one shown in Figure 13.
#define STDIN 0
#define U unsigned
#include <termio.h>
struct termio lock;
struct termio save;
U char sesame[82];
main()
{
ioctl(STDIN, TCGETA, &lock);
ioctl(STDIN, TCGETA, &save);
lock.c_iflag &= ~(BRKINT);
lock.c_lflag &= ~(ECHO|ISIG);
ioctl(STDIN, TCSETA, &lock);
while (checkpw())
printf("\r\nSorry, try again\r\n\r\n");
ioctl(STDIN, TCSETA, &save);
printf("\nYour password is %s", sesame);
}
checkpw()
{
int i;
U char *p;
printf("Enter password:");
i = read(STDIN, sesame, sizeof sesame - 1);
if (i < 0)
i = 0;
sesame[i] = '\0';
printf("\r\nYou entered:'");
for (p=sesame; p<sesame+i; p++)
if (*p >= ' ' && *p < 127)
printf("%c", *p);
else
printf("^%c", *p == 127 ?
'?' : *p + '@');
printf("'\r\n");
return strcmp(sesame, "open\n");
}
Figure 13 -- An example program for protecting against
abort signals.
rm -f /dev/lp
ln /dev/ttyd3 /dev/lp
sleep 1000000 < /dev/lp&
sleep 3
stty 1200 xon < /dev/lp&
Figure 14 -- Lines to add to
/etc/rc.
One final note. After all programs with open terminal devices have closed
their file descriptors, the device's modes revert back to a system default.
Thus to configure a printer for 1200 baud XON protocol the
lines listed in Figure 14 must be added to your /etc/rc file.
The first sleep keeps the device file open (for a while).
The second sleep allows time for the first one to get fired
up (since it runs in background). The stty sets the desired
modes. It should be run in background in case the printer is disconnected, in
which case it will not be asserting DTR, which will probably
lead the open to hang. You do not want this to freeze
invocation of your /etc/rc file and hence freeze your system.
Bob Toxen is a member of the technical staff at Silicon Graphics, Inc.
He has gained a reputation as a leading uucp expert and is
responsible for ports of System V for the Zilog 8000 and System III for the
Motorola 68000.
Copyright © 1984, 2007, 2014, 2020 Robert M. Toxen. All rights reserved.
Back
|