Inexpensive, effective mixed-platform Network Security using Linux-based solutions. 

 
Horizon Network Security™
phone: +1 770-662-8321, email: support@VerySecureLinux.com

 
Our Publications

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